精华内容
下载资源
问答
  • 火山安卓动态菜单.zip

    2021-04-27 11:29:47
    火山安卓动态菜单
  • 安卓动态桌面

    2012-02-20 12:32:30
    安卓动态桌面 可以设置MP4的视频于自己的安卓机器桌面之上!
  • 安卓动态布局小demo

    2017-02-17 10:48:55
    安卓动态布局小demo
  • 安卓动态时间自定义控件,可以直接获取当前时间 日期年份,实时刷新
  • 安卓动态壁纸制作

    2013-12-21 15:36:48
    安卓动态壁纸源代码,非常详细对于初学者帮助很大
  • 安卓动态隐藏虚拟菜单栏
  • 安卓动态加载ListView

    2017-08-24 09:27:00
    安卓动态加载ListView

    1、ListViewLoadingActivity

    public class ListViewLoadingActivity extends ListActivity implements
    		OnScrollListener {
    	private LinearLayout mLoadLayout;
    	private ListView mListView;
    	private ListViewAdapter mListViewAdapter;
    	private int mLastItem = 0;
    	private int mCount = 41;
    	private Handler mHandler = new Handler();
    	private LayoutParams mProgressBarLayoutParams = new LinearLayout.LayoutParams(
    			LinearLayout.LayoutParams.WRAP_CONTENT,
    			LinearLayout.LayoutParams.WRAP_CONTENT);
    	private LayoutParams mTipContentLayoutParams = new LinearLayout.LayoutParams(
    			LinearLayout.LayoutParams.WRAP_CONTENT,
    			LinearLayout.LayoutParams.WRAP_CONTENT);
    
    	@Override
    	public void onCreate(Bundle savedInstanceState) {
    		super.onCreate(savedInstanceState);
    		/**
    		 * "加载项"布局,此布局被添加到ListView的Footer中。
    		 */
    		mLoadLayout = new LinearLayout(this);
    		mLoadLayout.setMinimumHeight(60);
    		mLoadLayout.setGravity(Gravity.CENTER);
    		mLoadLayout.setOrientation(LinearLayout.HORIZONTAL);
    		/**
    		 * 向"加载项"布局中添加一个圆型进度条。
    		 */
    		ProgressBar mProgressBar = new ProgressBar(this);
    		mProgressBar.setPadding(0, 0, 15, 0);
    		mLoadLayout.addView(mProgressBar, mProgressBarLayoutParams);
    		/**
    		 * 向"加载项"布局中添加提示信息。
    		 */
    		TextView mTipContent = new TextView(this);
    		mTipContent.setText("加载中...");
    		mLoadLayout.addView(mTipContent, mTipContentLayoutParams);
    		/**
    		 * 获取ListView组件,并将"加载项"布局添加到ListView组件的Footer中。
    		 */
    		mListView = getListView();
    		mListView.addFooterView(mLoadLayout);
    		/**
    		 * 组ListView组件设置Adapter,并设置滑动监听事件。
    		 */
    		mListViewAdapter = new ListViewAdapter(ListViewLoadingActivity.this);
    		setListAdapter(mListViewAdapter);
    		mListView.setOnScrollListener(this);
    	}
    
    	/**
    	 * firstVisibleItem: 第一个可见Item在所有Item中的位置(即屏幕上显示的第一行,在你的数据数组中的位置)
    	 * visibleItemCount: 可见Item个数(屏幕内可以显示多少行)
    	 * totalItemCount: 总共有多少行数据
    	 */	
    	@Override
    	public void onScroll(AbsListView view, int firstVisibleItem,
    			int visibleItemCount, int totalItemCount) {
    		mLastItem = firstVisibleItem + visibleItemCount - 1;
    		if (mListViewAdapter.count > mCount) {
    			mListView.removeFooterView(mLoadLayout);
    		}
    	}
    
    	@Override
    	public void onScrollStateChanged(AbsListView view, int mScrollState) {
    
    		/**
    		 * 当ListView滑动到最后一条记录时这时,我们会看到已经被添加到ListView的"加载项"布局, 这时应该加载剩余数据。
    		 */
    		if (mLastItem == mListViewAdapter.count
    				&& mScrollState == OnScrollListener.SCROLL_STATE_IDLE) {
    			if (mListViewAdapter.count <= mCount) {
    				mHandler.postDelayed(new Runnable() {
    					@Override
    					public void run() {
    						mListViewAdapter.count += 10;
    						mListViewAdapter.notifyDataSetChanged();
    						//mListView.setSelection(mLastItem);
    					}
    				}, 1000);
    			}
    		}
    	}
    
    }

    2、ListViewAdapter

    class ListViewAdapter extends BaseAdapter {
    	int count = 10;
    	Context context;
    	
    	public ListViewAdapter (Context context) {
    		this.context=context;
    	}	
    	
    	public int getCount() {
    		return count;
    	}
    	public Object getItem(int position) {
    		return position;
    	}
    	public long getItemId(int position) {
    		return position;
    	}
    	public View getView(int position, View view, ViewGroup parent) {
    		TextView mTextView;
    		if (view == null) {
    			mTextView = new TextView(context);
    		} else {
    			mTextView = (TextView) view;
    		}
    		mTextView.setText("Item " + position);
    		mTextView.setTextSize(20f);
    		mTextView.setGravity(Gravity.CENTER);
    		mTextView.setHeight(60);
    		return mTextView;
    	}
    }


    展开全文
  • 安卓动态壁纸 水

    2012-12-24 23:08:55
    安卓动态壁纸 水 水位高低表示电量,充电时会从桌面底部冒气泡,手指触碰屏幕也会出现气泡。水面随重力感应
  • 安卓动态修改文件名称及图标
  • 安卓动态申请权限

    2020-05-31 11:31:29
    安卓动态申请权限 if(Build.VERSION.SDK_INT>=23){ ActivityCompat.requestPermissions(this,new String[]{ Manifest.permission.CAMERA,Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission....

    安卓动态申请权限

     if(Build.VERSION.SDK_INT>=23){
                ActivityCompat.requestPermissions(this,new String[]{
                        Manifest.permission.CAMERA,Manifest.permission.READ_EXTERNAL_STORAGE,
                        Manifest.permission.WRITE_EXTERNAL_STORAGE
                },1);
            }
    
    展开全文
  • 安卓动态壁纸DIY,DIY_LiveWallpapers安卓动态壁纸DIY,用于制作安卓动态壁纸,自己动手,丰衣足食
  • 安卓 动态折线图

    2018-11-12 22:46:24
    安卓 动态折线图 一、简述  记--一个简单的动态折线图。数据为随机数。  例子打包:链接: https://pan.baidu.com/s/12IdD6eayEvRPeFvoymuCcg 提取码: 9vu7  二、效果   三、工程结构 四、源文件 ...

    安卓 动态折线图

    一、简述

          记--一个简单的动态折线图。数据为随机数。

          例子打包:链接: https://pan.baidu.com/s/12IdD6eayEvRPeFvoymuCcg 提取码: 9vu7 

    二、效果

                                             

    三、工程结构

    四、源文件

    MainActivity.java文件

    package com.liang.chart;
    
    import java.util.Random;
    
    import org.achartengine.chart.PointStyle;
    
    import android.os.Bundle;
    import android.os.Handler;
    import android.os.Message;
    import android.view.View;
    import android.widget.Button;
    import android.widget.Toast;
    import android.app.Activity;
    import android.graphics.Color;
    
    public class MainActivity extends Activity {
    
    	private LineChart mLineChart;//直线图类
    	private boolean addData_thread_run; // 控制添加折线图数据线程的退出
    	private int x_index;// X轴的刻度值
    	private Random random;// 用来获取随机数
    	
    	@Override
    	protected void onCreate(Bundle savedInstanceState) {
    		super.onCreate(savedInstanceState);
    		setContentView(R.layout.activity_main);//设置主界面
    		
    		//创建折线图实例 (X轴标题,Y轴标题,X轴的最小值,X轴的最大值,Y轴的最小值,Y轴的最大值,坐标轴的颜色,刻度值的颜色)
    		mLineChart = new LineChart("时间(min)", "", 0, 100, 0, 9500, Color.WHITE, Color.WHITE);
    		
    		x_index = 50;//初始化X轴的刻度值
    		random = new Random();
    	};
    	
    	@Override
    	protected void onResume() //在本页面onStart()之后设置为绘图所在的页面
    	{
    		super.onResume();
    		
    		//设置图表显示页面为本页面
    		mLineChart.setChartViewActivity(this);
    		
    		//添加4条折线
    		mLineChart.addLineToChart("折线A", PointStyle.CIRCLE, Color.BLUE);//添加折线A
    		mLineChart.addLineToChart("折线B", PointStyle.DIAMOND, Color.GREEN);//添加折线B
    		mLineChart.addLineToChart("折线C", PointStyle.TRIANGLE, Color.CYAN);//添加折线C
    		mLineChart.addLineToChart("折线D", PointStyle.SQUARE, Color.YELLOW);//添加折线D
    		
    	}
    	
    	// 消息句柄(线程里无法进行界面更新,所以要把消息从线程里发送出来在消息句柄里进行处理)
    		public Handler mHandler = new Handler() 
    		{
    			@Override
    			public void handleMessage(Message msg) 
    			{
    				switch (msg.what) 
    				{
    				case 0:
    					//添加数据 (添加折点)
    					mLineChart.addData("折线A", x_index, random.nextInt(9000) );
    					mLineChart.addData("折线B", x_index, random.nextInt(9000) );
    					mLineChart.addData("折线C", x_index, random.nextInt(9000) );
    					mLineChart.addData("折线D", x_index, random.nextInt(9000) );
    					
    					
    					x_index += 10; // X轴每次右移10个刻度
    					mLineChart.moveChart(10);// 图标右移10刻度值
    					
    					//绘制折线图(更新UI)
    					mLineChart.mChartView.repaint();
    					break;
    				}
    			}
    		};
    	
    	// "开始"按钮的点击响应动作
    	public void onButtonStartClicked(View v)
    	{
    		Button btn_start = (Button)v;// 拿到按钮句柄
    		if( btn_start.getText().toString().equals("开始") )
    		{// 点击的是"开始"
    			new Thread(addData_thread).start() ;// 开启子线程,开始动态的添加数据
    			btn_start.setText("停止");// 设置按钮文本为 "停止"
    		}
    		else // 点击的是"停止"
    		{
    			addData_thread_run = false;// 结束子线程,停止添加数据
    			btn_start.setText("开始");// 设置按钮文本为 "开始"
    		}
    		
    	}
    	
    	//添加折线图数据的线程
    	private Runnable addData_thread = new Runnable()
    	{
    		@Override
    		public void run()
    		{
    			
    			addData_thread_run = true;
    			while(addData_thread_run)
    			{
    				try {
    					Thread.sleep(1000);// 延时1秒
    					mHandler.sendEmptyMessage(0);// 发送0类型信息,通知主线程更新图表
    					
    				} catch (InterruptedException e) 
    				{
    					break;
    				}
    			}
    		}
    	};
    
    }//end with MainActivity
    

    LineChart.java文件

    package com.liang.chart;
    
    import java.util.ArrayList;
    import java.util.List;
    
    import org.achartengine.ChartFactory;
    import org.achartengine.GraphicalView;
    import org.achartengine.chart.PointStyle;
    import org.achartengine.model.SeriesSelection;
    import org.achartengine.model.XYMultipleSeriesDataset;
    import org.achartengine.model.XYSeries;
    import org.achartengine.renderer.XYMultipleSeriesRenderer;
    import org.achartengine.renderer.XYSeriesRenderer;
    
    import com.liang.chart.R;
    
    import android.app.Activity;
    import android.graphics.Color;
    import android.graphics.Paint.Align;
    import android.view.View;
    import android.view.ViewGroup.LayoutParams;
    import android.widget.LinearLayout;
    import android.widget.Toast;
    
    //折线图 类
    public class LineChart 
    {
    	// 渲染数据集 (图表的数据,折线的折点坐标信息等)
    	private XYMultipleSeriesDataset mDataset;
    	
    	// 多重图层渲染器 (可以看做是总渲染器(含多个子渲染器),绘制背景网格,折线图等)
    	private XYMultipleSeriesRenderer mMltRenderer;
    	
    	// 折线集合
    	private List<XYSeries> mSeriesList;
    		
    	
    	// 标记折线图是否可以拖动
    	public boolean mIsCanMove = true;
    
    	// 图表视图(就是最后绘制出来的整个折线图)
    	public GraphicalView mChartView;
    
    	//无参构造函数 (如果有 有参数的构造函数,就不会自动添加无参构造函数,最好自己加上无参构造函数,)
    	public LineChart(){}
    	
    
    	/* 有参构造函数
    	 *   设置图表属性
    	 *   xTitle:X轴标题
    	 *   yTitle:Y轴标题
    	 *   xMin:X轴的最小值
    	 *   xMax:X轴的最大值
    	 *   yMin:Y轴的最小值
    	 *   yMax:Y轴的最大值
    	 *   axisColor:坐标轴的颜色 
    	 *   labelsColor:标签的颜色(标签:也就是坐标轴上的刻度值10,20...80)
    	 */
    	public LineChart(String xTitle, String yTitle, double xMin,
    			double xMax, double yMin, double yMax, int axisColor, int labelsColor) 
    	{
    		mDataset = new XYMultipleSeriesDataset();// 实例化数据集对象
    		mMltRenderer = new XYMultipleSeriesRenderer();// 实例化多层渲染器对象
    		mSeriesList = new ArrayList<XYSeries>(); // 初始化折线集合
    		
    		mMltRenderer.setXTitle(xTitle);// 设置X轴标题
    		mMltRenderer.setYTitle(yTitle);// 设置Y轴标题
    		mMltRenderer.setXAxisMin(xMin);// 设置X轴的最小值
    		mMltRenderer.setXAxisMax(xMax);// 设置X轴的最大值
    		mMltRenderer.setYAxisMin(yMin);// 设置Y轴的最小值
    		mMltRenderer.setYAxisMax(yMax);// 设置Y轴的最大值
    		mMltRenderer.setAxesColor(axisColor);// 设置坐标轴颜色
    		mMltRenderer.setLabelsColor(labelsColor);// 设置标签(刻度值)颜色
    		mMltRenderer.setShowGrid(true);// 显示网格
    		mMltRenderer.setGridColor(Color.GRAY);// 设置网格颜色
    		mMltRenderer.setXLabels(10);// 设置X轴的标签数(有几个刻度)
    		mMltRenderer.setXLabelsPadding(10);//设置X轴标签的间距
    		mMltRenderer.setYLabels(16);// 设置Y轴的标签数
    		mMltRenderer.setYLabelsAlign(Align.RIGHT);// 设置Y轴标签的方向
    		mMltRenderer.setPointSize((float) 2);//设置折线点的大小
    		mMltRenderer.setShowLegend(true);// 下面的 图例标注,如圆点的蓝色的折线是X轴...
    		mMltRenderer.setZoomButtonsVisible(false);// 隐藏放大缩小按钮
    		mMltRenderer.setZoomEnabled(true, false);// 设置缩放,这边是横向可以缩放,竖向不能缩放
    		mMltRenderer.setPanEnabled(true, false);// 设置滑动,这边是横向可以滑动,竖向不可滑动
    		
    	}
    
    	//添加一条折线到图表
    	public void addLineToChart(String lineTitle, PointStyle pointStyle, int lineColor)
    	{
    		XYSeriesRenderer serRender = new XYSeriesRenderer();//创建1个子渲染器
    		XYSeries series = new XYSeries(lineTitle);//创建1条折线
    		
    		mMltRenderer.addSeriesRenderer(serRender);// 将子渲染器添加到总渲染器
    		mDataset.addSeries(series);// 将折线添加到数据集
    		mSeriesList.add(series);// 将折线添加到折线集合
    		
    		// 设置折线渲染属性
    		serRender.setPointStyle(pointStyle);// 设置折点的样式
    		serRender.setColor(lineColor);// 设置折线的颜色
    		serRender.setFillPoints(true);// 设置折点是实心还是空心
    		serRender.setDisplayChartValues(false);// 不显示折点的Y值
    		serRender.setDisplayChartValuesDistance(10);// 设置数值与折点的距离
    		
    	}
    
    	//设置图表的显示页面 (activity:图表显示所在的页面)
    	public void setChartViewActivity(final Activity activity) 
    	{
    		if (mChartView == null) 
    		{
    			//获取一个layout,用来显示图表
    			LinearLayout layout = (LinearLayout) activity.findViewById(R.id.chart);
    			//生成图表
    			mChartView = ChartFactory.getLineChartView(activity, mDataset,mMltRenderer);
    			
    			mMltRenderer.setClickEnabled(true);// 可以响应点击
    			mMltRenderer.setSelectableBuffer(10);// 设置点的缓冲半径值(在某点附近点击时,在点的半径范围内都算点击这个点)
    			
    			//折线的点击响应
    			mChartView.setOnClickListener(new View.OnClickListener() {
    				public void onClick(View v) {
    					// 拿到点击的折线对象、折点
    					SeriesSelection seriesSelection = mChartView.getCurrentSeriesAndPoint();
    					if (seriesSelection != null) 
    					{
    						// 将所点击的点的信息通过Toast展示(点击的是那一条折线,第几个折点,坐标值)
    						Toast.makeText(activity,
    								  "折线:"+ seriesSelection.getSeriesIndex()
    								+ "\n点: "+ seriesSelection.getPointIndex()
    								+ "\nX="+ seriesSelection.getXValue() + ", Y="+ seriesSelection.getValue(),
    								Toast.LENGTH_SHORT).show();
    					}
    				}
    			});
    			
    			// 将图表添加到layout中
    			layout.addView(mChartView, new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
    		} 
    		else 
    		{
    			mChartView.repaint();//绘制折线图(重绘,更新)
    		}
    	}
    
    	// 向某条折线添加折点(x,y) (添加数据)lineTitle:折线的标题
    	public void addData(String lineTitle, double x, double y) 
    	{
    		if(mSeriesList.size() > 0 )//有折线
    		{
    			for(XYSeries ser : mSeriesList)//遍历折线集合
    			{
    				if(ser.getTitle().equals(lineTitle))// 找到指定的折线
    				{
    					ser.add(x, y);
    					break;
    				}
    			}
    		}
    	}
    	
    	// 拖动图表  设置X轴的当前显示向右移val
    	public void moveChart(int val) 
    	{
    		// 计算当前X轴可视长度,也就是当前看到的X轴上的右刻度与左刻度之差(缩放按钮能够影响看到的刻度值范围)
    		double dis = mMltRenderer.getXAxisMax() - mMltRenderer.getXAxisMin();
    		
    		double max = val + mMltRenderer.getXAxisMax();
    		double min = max - dis;
    		mMltRenderer.setXAxisMin(min);// 设置X轴显示的左刻度值
    		mMltRenderer.setXAxisMax(max);// 设置X轴显示的右刻度值
    		
    	}
    }
    

    布局文件

    activity_main.xml文件

    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity" >
    
         <LinearLayout
             android:id="@+id/linear_layout"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:orientation="horizontal" >
    
    		<Button
    		    android:id="@+id/btn_start"
    		    android:layout_width="wrap_content"
    		    android:layout_height="wrap_content"
    		    android:onClick="onButtonStartClicked"
    		    android:text="开始" />
    
        </LinearLayout>
    
         <LinearLayout
             android:id="@+id/chart"
             android:layout_width="fill_parent"
             android:layout_height="300dp"
             android:layout_alignParentBottom="true"
             android:layout_alignParentLeft="true"
             android:layout_below="@+id/linear_layout"
             android:orientation="horizontal" >
    
         </LinearLayout>
    	 
    </RelativeLayout>
    

     

    五、总结

         1、产生指定范围的随机数

    Random random = new Random();
    int num = random.nextInt(n);// 作用是生成一个随机的int值,该值介于[0,n)的区间,也就是0到n之间的随机int值,包含0而不包含n。
    
    // 生成指定范围的随机数
    int num = random.nextInt(b)%(b-a+1) + a;//生成[a,b],包含a,b
    
    // 如生成[1,100],即1到100,包括1和100
    int a = 1;
    int b = 100; 
    int num = random.nextInt(b)%(b-a+1) + a;

       2、结束线程:使用控制变量来结束线程循环 或者用stop()方法(好像说不安全)

       3、线程一般不能二次start(),所以例子中每次开始新建子线程。

    展开全文
  • 安卓动态调试

    2016-01-08 10:05:01
    随着移动安全越来越火,各种调试工具也都层出不穷,但因为环境和需求的不同,并没有工具是万能的。另外工具是死的,人是活的,如果能搞懂工具的原理再结合上自身的经验...安卓动态调试七种武器之长生剑 - Smali Instrum

    随着移动安全越来越火,各种调试工具也都层出不穷,但因为环境和需求的不同,并没有工具是万能的。另外工具是死的,人是活的,如果能搞懂工具的原理再结合上自身的经验,你也可以创造出属于自己的调试武器。因此,笔者将会在这一系列文章中分享一些自己经常用或原创的调试工具以及手段,希望能对国内移动安全的研究起到一些催化剂的作用。

    目录如下:

    安卓动态调试七种武器之长生剑 - Smali Instrumentation

    安卓动态调试七种武器之孔雀翎 – Ida Pro

    安卓动态调试七种武器之离别钩 - Hooking

    安卓动态调试七种武器之碧玉刀- Customized DVM

    安卓动态调试七种武器之多情环- Customized Kernel

    安卓动态调试七种武器之霸王枪 - Anti Anti-debugging

    安卓动态调试七种武器之拳头 - Tricks & Summary

    0x01 孔雀翎


    天下的暗器共有三百六十余种,但其中最成功、最可怕的就是孔雀翎。它使用简单,却威力无边。据说,孔雀翎发动之时,暗器四射,有如孔雀开屏,辉煌灿烂,而就在敌人目眩神迷之际,便已魂飞魄散。这武器的描述与Ida是何其的相似啊!所以说安卓动态调试七种武器中的孔雀翎非Ida莫属。因为Ida太有名了,相应的教程也是漫天飞,但很多并不是安卓相关的内容,所以笔者决定将一些经典的安卓调试技巧总结归纳一下。因为篇幅原因,笔者并不能保证本文能够覆盖到ida调试的方方面面,看官如有兴趣可以再继续深入研究学习。

    0x02 还原JNI函数方法名


    在android调试中,你会经常见到这种类型的函数:

    enter image description here

    首先是一个指针加上一个数字,比如v3+676。然后将这个地址作为一个方法指针进行方法调用,并且第一个参数就是指针自己,比如(v3+676)(v3…)。这实际上就是我们在JNI里经常用到的JNIEnv方法。因为Ida并不会自动的对这些方法进行识别,所以当我们对so文件进行调试的时候经常会见到却搞不清楚这个函数究竟在干什么,因为这个函数实在是太抽象了。解决方法非常简单,只需要对JNIEnv指针做一个类型转换即可。比如说上面提到v3指针,我们选中后按一下”y”键,然后将类型声明为”JNIEnv*”。

    enter image description here

    随后IDA就会自动查找对应的方法并且显示出来了:

    enter image description here

    是不是瞬间清晰了很多?另外有人( 貌似是看雪论坛上的)还总结了所有JNIEnv方法对应的数字,地址以及方法声明:

    enter image description here

    有兴趣的同学可以去我的github下载。

    0x03 调试.init_array和JNI_OnLoad


    我们知道so文件在被加载的时候会首先执行.init_array中的函数,然后再执行JNI_OnLoad()函数。JNI_Onload()函数因为有符号表所以非常容易找到,但是.init_array里的函数需要自己去找一下。首先打开view ->Open subviews->Segments。然后点击.init.array就可以看到.init_array中的函数了。

    enter image description here

    enter image description here

    enter image description here

    但一般当我们使用ida进行attach的时候,.init_array和JNI_Onload()早已经执行完毕了,根本来不急调试。这时候我们可以使用jdb这个工具来解决,这个工具是安装完jdk以后自带的,可以在jdk的bin目录下找到。在这里我们使用阿里移动安全挑战赛2014的第二题作为例子讲解一下如何调试JNI_OnLoad()。

    打开程序后,界面是这样的:

    enter image description here

    我们的目标就是获取到密码。使用ida反编译一下so文件会看到我们输入后的密码会和off_628c这个指针指向的字符串进行比较。

    enter image description here

    于是我们查看off_628c这个地址对应的指针,发现对应的字符串是”wojiushidaan”。

    enter image description here

    enter image description here

    于是我们把这个密码输入一下,发现密码错误。看样子so文件在加载的时候对密码字符串进行了动态修改。既然动态修改了那我们用ida动态调试一下好了,我们打开程序,然后再用ida attach一下,发现程序直接闪退了,ida那边也没有任何有用信息。原来这就是自毁程序的意思啊。既然如此我们动态调试一下JNI_OnLoad()来看一下程序究竟做了什么吧。步骤如下:

    1 ddms

    一定要打开ddms,否则调试端口是关闭的,就无法在程序刚开始的暂停了。我之前不知道要打开ddms才能用jdb,还以为android系统或者sdk出问题了,重装好几次。汗。

    2 adb push androidserver /data/local/tmp/

    adb shell
    su
    chmod 777 /data/local/tmp/androidserver
    /data/local/tmp/androidserver
    

    这里我们把ida的androidserver push到手机上,并以root身份执行。

    3 adb forward tcp:23946 tcp:23946

    将ida的调试端口进行转发,这样pc端的ida才能连接手机。

    4 adb shell am start -D -n com.yaotong.crackme/.MainActivity

    这里我们以debug模式启动程序。程序会出现waiting for debugger的调试界面。

    5 ida attach target app

    这时候我们启动ida并attach这个app的进程。

    6 suspend on libary loading

    我们在debugger setup里勾选 suspend on library load。然后点击继续。

    enter image description here

    7 jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=8700

    用jdb将app恢复执行。

    8 add breakpoint at JNI_OnLoad

    随后程序会在加载libcrackme.so这个so文件的时候停住。这时候ida会出现找不到文件的提示,不用管他,点取消即可。随后就能在modules中看到libcrackme.so这个so文件了,我们点进去,然后在JNI_OnLoad处下个断点,然后点击执行,程序就进入了JNI_OnLoad()这个函数。

    PS:有时候你明明在一个函数中却无法F5,这时候你需要先按一下”p”键,程序会将这段代码作为函数分析,然后再按一下”F5”,你就能够看到反汇编的函数了。

    因为过程有点繁琐,我录制了一个调试JNI_OnLoad()的视频在我的github,有兴趣的同学可以去下载观看。因为涉及到其他的技巧,我们将会在随后的”ida双开定位”章节中继续讲解如何调试.init_array中的函数。

    0x04 Ida双开定位


    Ida双开定位的意思是先用ida静态分析so文件,然后再开一个ida动态调试so文件。因为在动态调试中ida并不会对整个动态加载的so文件进行详细的分析,所以很多函数并无法识别出来。比如静态分析中有很多的sub_XXXX函数:

    enter image description here

    但动态调试中的ida是没有这些信息的。

    enter image description here

    所以我们需要双开ida,然后通过ida静态分析的内容来定位ida动态调试的函数。当然很多时候我们也需要动态调试的信息来帮助理解静态分析的函数。

    在上一节中,我们提到.init.array中有个sub_2378(),但当ida动态加载so后我们并无法在module中找到这个函数。那该咋办呢?这时候我们就要通过静态分析的地址和so文件在内存中的基址来定位目标函数。首先我们看到sub_2378()这个函数在静态分析中的地址为.text:00002378。而在动态加载中这个so在内存中的基址为:4004F000。

    enter image description here

    因此sub_2378()这个函数在内存中真正的地址应该为4004F000 + 00002378 =40051378。下面我们在动态调试窗口输入”g”,跳转到40051378这个地址。然后发现全是乱码的节奏:

    enter image description here

    不要担心,这是因为ida认为这里是数据段。这时候我们只要按”P”或者选中部分数据按”c”,ida就会把这段数据当成汇编代码进行分析了:

    enter image description here

    我们随后还可以按”F5”,将汇编代码反编译为c语言。

    enter image description here

    是不是和静态分析中的sub_2378()长的差不多?

    enter image description here

    我们随后可以在这个位置加入断点,再结合上一节提到的调试技巧就可以对init.array中的函数进行动态调试了。

    我们接下来继续分析自毁程序这道题,当我们在对init.array和JNI_OnLoad()进行调试的时候,发现程序在执行完dowrd_400552B4()后就挂掉了。

    enter image description here

    于是我们在这里按”F7”进入函数看一下:

    enter image description here

    原来是libc.so的phread_create()函数,估计是app本身开了一个新的线程进行反调试检测了。

    有意思的是在静态分析中我们并不清楚dword_62B4这个函数是做什么的,因为这个函数的地址在.bss段还没有被初始化:

    enter image description here

    enter image description here

    但是当我们动态调试的时候,这个地址的值已经修改为了phread_create()这个函数的地址了。:

    enter image description here

    enter image description here

    所以说自毁程序密码这个app会用pthread_create()开一个新的线程对app进行反调试检测。线程会运行sub_16A4()这个函数。于是我们对这个函数进行分析,发现里面的内容有大量的混淆,看起来十分吃力。这里我介绍个小trick:常见的反调试方法都会用fopen打开一些文件来检测自己的进程是否被attach,比如说status这个文件中的tracerpid的值是否为0,如果为0说明没有别的进程在调试这个进程,如果不为0说明有程序在调试。所以我们可以守株待兔,在libc.so中的fopen()处下一个断点,然后我们在hex view窗口中设置数据与R0的值同步:

    enter image description here

    这样的话,当函数在fopen处停住的时候我们就能看到程序打开了哪些文件。果不其然,程序打开了/proc/[pid]/status这个文件。我们”F8”继续执行fopen函数,看看返回后的地址在哪。然后发现我们程序卡在了某个函数中间,PC上面都是数据,PC下面才是汇编。这该咋办呢?

    enter image description here

    解决办法还是ida双开,我们知道现在PC的地址为40050420,libcrackme.so文件的基址为4004F000。因此这段代码在so中的位置应该是:40050420 - 4004F000 = 1420。因此我们回到ida静态分析界面,就可以定位到我们其实是在sub_130C()这个函数中。于是我们猜测这个函数就是用来做反调试检测的。

    enter image description here

    因此我们可以通过基址来定位sub_130C()这个函数在内存中的地址:40050420 + 130C = 4005030C。然后我们在4005030C这个地址处按”P”, ida就可以正确的识别整个函数了。

    enter image description here

    所以说动态调试的时候可以帮我们了解到很多静态分析很难获取到的信息。这也就是ida双开的意义所在:静态帮助动态定位函数地址,动态帮助静态获取运行时信息。

    0x05 Ida动态修改内存数据和寄存器数值


    我们继续分析自毁程序密码这个app,我们发现该程序会用fopen ()打开/proc/[pid]/status这个文件,随后会用fgets()和strstr()来获取,于是我们在strstr()处下个断点,然后让hex view的数据与R0同步。每次点击继续,我们都会看到strstr传入的参数。当传入的参数变为TracerPid:XXXX的时候我们停一下。因为在正常情况下,TracerPid的值应该是0。但是当被调试的时候就会变成调试器的pid。

    enter image description here

    enter image description here

    为了防止程序发现我们在调试,在这里我们需要把值改回0。我们在hex view的2那里点击右键,然后选择edit。随后我们输入30和00,再点击”apply changes”。就可以把TracerPid改为0了。然后就可以bypass这一次的反调试的检测。

    enter image description here

    但这个程序检测TracerPid的次数非常频繁,我们要不断的修改TracerPid的值才行,这种方法实在有点治标不治本,所以我们会在下一节介绍patch so文件的方法来解决这个问题。

    另外在ida动态调试过程中,除了内存中的数据可以修改,寄存器的数据也是可以动态修改的。比如说程序执行到CMP R6, #0。本来R6的值是0,经过比较后,程序会跳转到4082A3FC这个地址。

    enter image description here

    但是如果我们在PC执行到4082A1F8这条语句的时候,将R6的值动态修改为0。程序就不会进行跳转了。

    enter image description here

    enter image description here

    你甚至可以修改PC寄存器的值来控制程序跳转到任何想要跳转到的位置,简直和ROP的原理一样。但记得要注意栈平衡等问题。

    enter image description here

    0x06 Patch so文件


    在上文中,我们通过分析定位到sub_130C()这个函数有很大可能性是用来做反调试检测的,并且作者开了一个新的线程,并且用了一个while来不断执行sub_130C()这个函数,所以说我们每次手动的修改TracerPid实在是不现实。

    enter image description here

    既然如此我们何不把sub_130C()这个函数给nop掉呢?为了防止nop出错,我们先在”F5”界面选择所有代码,然后用”Copy to assembly”功能,就可以把c语言代码注释到汇编代码里。

    enter image description here

    enter image description here

    在这里我们看到如果想要注释掉sub_130C()函数,只需要注释掉000016B8这个位置上的代码即可,如果我们想要注释掉dword_62B0(3)这个函数,我们则需要注释掉000016BC-000016C4这三个位置上的代码。接下来我们选中000016B8这一行,然后再点击HexView。HexView会帮我们自动定位到000016B8这个位置。

    enter image description here

    因为ARM是没有单独的NOP指令的。于是我们采用movs r0,r0作为NOP。对应的机器码为”00 00 A0 E1”。所以我们把”13 FF FF EB”这段内容修改为”00 00 A0 E1”。

    enter image description here

    我们再回”F5”界面,就会发现sub_130C()函数已经没有了。

    enter image description here

    最后我们点击”Edit->Plugins->modifyfile”,然后就可以保存新的so文件了。我们将这个so文件覆盖原apk中的so文件,然后再重新签名。

    enter image description here

    这次我们先运行程序,再用ida加载,app并没有闪退,说明我们patch成功了。于是我们先在”Java_com_yaotong_crackme_MainActivity_securityCheck”处下断点。然后在app随便输入一个密码,点击app上的”输入密码”按钮。

    enter image description here

    程序就会暂停在”Java_com_yaotong_crackme_MainActivity_securityCheck”处。我们先按”P”再按”F5”,就可以看到反汇编的c语言了。而这里的unk_4005228C就是保存了密码字符串指针的指针。

    enter image description here

    因为是指针的指针,所以我们先双击进入这个地址。

    enter image description here

    然后在这个地址上按三下”D”,将这里的数据格式从字符转化为指针形式。

    enter image description here

    然后我们再双击进入这个地址,就可以看到最后的flag了。答案是”aiyou,bucuoo”。

    enter image description here

    这道题里我们只是用到了很简单的patch so技巧,在实战中我们不光可以NOP,我们还可以改变条件判断语句,比如将”BNE”变为” BEQ”。我们甚至可以修改跳转地址,比如直接让程序B到某个地址去执行,这样的话就不需要挨个的NOP很多语句了。要注意的是,ARM中的跳转指令是根据相对地址计算的,所以你要根据当前指令地址和目标地址来计算出相对跳转的值。

    比如说00001BCC: BEQ loc_1C28对应的汇编代码为”15 00 00 0A”。

    enter image description here

    enter image description here

    0x0A代表BEQ,”15 00 00”代表跳转的相对地址,因为在arm中pc的值是当前指令的下两条(下一条的下一条)指令的地址,所以我们需要将0x15再加上2。随后就可以计算出最后跳转到的地址: (0x15 + 0x2)*4 + 0x1BCC = 0x1C28。Ida反汇编后的结果也验证了结果是BEQ loc_1C28。

    接下来我们想修改汇编代码为00001BCC: BNE loc_1C2C。只需要将”0A”变成”1A”,将”15”变成”16”即可。

    enter image description here

    enter image description here

    0x0A代表BEQ,”15 00 00”代表跳转的相对地址,因为在arm中pc的值是当前指令的下两条(下一条的下一条)指令的地址,所以我们需要将0x15再加上2。随后就可以计算出最后跳转到的地址: (0x15 + 0x2)*4 + 0x1BCC = 0x1C28。Ida反汇编后的结果也验证了结果是BEQ loc_1C28。

    接下来我们想修改汇编代码为00001BCC: BNE loc_1C2C。只需要将”0A”变成”1A”,将”15”变成”16”即可。

    0x07 Kill调试技巧


    该技巧是QEver 在《MSC的伪解题报告》中提到的。利用kill我们可以让程序挂起,然后用ida挂载上去,获取有用的信息,然后可以再用kill将程序恢复运行。我们还是拿自毁程序密码这个应用举例,具体实行方法如下:

    1 首先用ps获取运行的app的pid。

    enter image description here

    2 然后用kill -19 [pid] 就可以将这个app挂起了。

    enter image description here

    3 随后我们用ida attach上这个app。因为整个进程都挂起了,所以这次ida挂载后app并没有闪退。然后就可以在内存中找到答案了。

    enter image description here

    4 如果想要恢复app的运行,需要将ida退出,然后再使用kill -18 [pid]即可。

    enter image description here

    0x08 在内存中dump Dex文件


    在现在的移动安全环境中,程序加壳已经成为家常便饭了,如果不会脱壳简直没法在破解界混的节奏。ZJDroid作为一种万能脱壳器是非常好用的,但是当作者公开发布这个项目后就遭到了各种加壳器的针对,比如说抢占ZJDroid的广播接收器让ZJDroid无法接收命令等。我们也会在”安卓动态调试七种武器之多情环 - Customized DVM”这篇文章中介绍另一种架构的万能脱壳器。但工具就是工具,当我们发布的时候可能也会遭到类似ZJDroid那样的针对。所以说手动脱壳这项技能还是需要学习的。在这一节中我们会介绍一下最基本的内存dump流程。在随后的文章中我们会介绍更多的技巧。

    这里我们拿alictf2014中的apk300作为例子来介绍一下ida脱简单壳的基本流程。 首先我们用调试JNI_OnLoad的技巧将程序在运行前挂起:

    adb shell am start -D -n com.ali.tg.testapp/.MainActivity
    

    ![enter image description here][59]

    然后在libdvm.so中的dvmDexFileOpenPartial函数上下一个断点:

    enter image description here

    然后我们点击继续运行,程序就会在dvmDexFileOpenPartial()这个函数处暂停,R0寄存器指向的地址就是dex文件在内存中的地址,R1寄存器就是dex文件的大小:

    enter image description here

    enter image description here

    然后我们就可以使用ida的script command去dump内存中的dex文件了。

    enter image description here

    enter image description here

    1
    2
    3
    4
    5
    6
    7
    8
    9
    static main(void)
    {
      auto fp, begin, end, dexbyte;
      fp = fopen("C:\\dump.dex", "wb");
      begin = r0;
      end = r0 + r1;
      for ( dexbyte = begin; dexbyte < end; dexbyte ++ )
          fputc(Byte(dexbyte), fp);
    }

    Dump完dex文件后,我们就可以用baksmali来反编译这个dex文件了。

    enter image description here

    因为过程有点繁琐,我录制了一个dump dex文件的视频在我的github,有兴趣的同学可以去下载观看。

    当然这只是最简单脱壳方法,很多高级壳会动态修改dex的结构体,比如将codeoffset指向内存中的其他地址,这样的话你dump出来的dex文件其实是不完整的,因为代码段保存在了内存中的其他位置。但你不用担心,我们会在随后的文章中介绍一种非常简单的解决方案,敬请期待。

    0x09 Function Rewrite函数重写


    有时我们想要将app中的某个函数的逻辑提取出来,用gcc重新编译一个可执行文件,比如我们想要写一个注册机,就需要把app生成key的逻辑提取出来。但是ida ”F5”过后的c语言直接编译经常会有很多错误,比如未定义的宏,未定义的声明等。这是因为这些宏都在ida的一个头文件里。里面定义了所有ida自定义的宏和声明,比如说经常见到的BYTEn()宏:

    1
    2
    3
    #define BYTEn(x, n)   (*((_BYTE*)&(x)+n))
    #define BYTE1(x)   BYTEn(x,  1)         // byte 1 (counting from 0)
    #define BYTE2(x)   BYTEn(x,  2)

    加上这个”defs.h”头文件后就可以正常的编译ida ”F5”后的c语言了。

    另外我们还可以自己创建一个NDK项目,然后自己编写一个so或者elf利用dlopen()和dlsym()调用目标so中的函数。比如我们想要调用libdvm.so中的dvmGetCurrentJNIMethod()函数,我们就可以在我们的NDK项目中这么写:

    1
    2
    3
    4
    5
    typedef void* (*dvmGetCurrentJNIMethod_func)();
    dvmGetCurrentJNIMethod_func dvmGetCurrentJNIMethod_fnPtr;
    dvm_hand= dlopen("libdvm.so", RTLD_NOW);
    dvmGetCurrentJNIMethod_fnPtr =dlsym(dvm_hand, "_Z22dvmGetCurrentJNIMethodv");
    dvmGetCurrentJNIMethod_fnPtr();

    0x10 小结


    还是那句话,写了这么多依然不能保证本文能够覆盖到ida调试的方方面面,因为ida实在是太博大精深了。看官如有兴趣可以继续深入研究学习。另外文章中所有提到的代码和工具都可以在我的github下载到,地址是:

    https://github.com/zhengmin1989/TheSevenWeapons
    展开全文
  • 安卓动态折线图

    2018-10-21 14:04:39
    实现在安卓动态显示折线图 效果。
  • DELPHI安卓动态权限申请 安卓8.0以前的版本,只需要给静态权限就可以了,但安卓8.0及以后的版本,还需要运行期用代码动态申请权限。 下面以《蓝牙权限》为例,其他权限类似。 Delphi 10.3 社区版,提供的 Sample ...
  • 安卓动态权限的获取

    千次阅读 2019-06-20 12:32:16
    终于找出一个时间,来整理一下,安卓动态权限的获取。仅以此文,记录一下。 首先,拍照权限是比较特殊的,一会儿会一次为例。 首先第一步,在配置文件中,添加所需的文件。 第二步:此处是针对于 拍照权限。 ...
  • 用于安卓学习,这个事例主要演示安卓按钮的动态生成与动态取消,可以很好地帮助安卓学者对安卓的简单认识.
  • 安卓动态换肤

    2017-06-03 17:08:03
    动态加载未安装的apk中的资源文件,实现类似QQ换肤效果
  • 安卓动态注册广播接收器demo(含注释),有动态注册广播接收器,发送自定义广播,接收系统广播等案例
  • 安卓动态调试方法

    2015-07-09 10:22:00
    安卓动态调试方法: 然后这样子 然后填一下参数: 然后附加进程 然后找到对比静态分析 总的来说就是1.把ida目录下android_server传到android目录中如:adbpushandroid_server/data/local/tmp/adbshell...
  • 目前遇到的问题是当我在标准接口中写安卓动态加载的方法DexClassLoader()时, 最后一个参数写getClassLoader()报错,如图: ![图片说明](https://img-ask.csdn.net/upload/201508/22/1440240913_76495.png) 错误...
  • 有木有前辈做过安卓动态人脸识别的例子,就是摇摇头,眨眨眼,张嘴这种,这个是要自己写的,还是第三方sdk提供了例子。目前用过讯飞的,但是是静态的。有做过这方面的,求指点?
  • 安卓动态调试的课题太大,先说一下Java语言吧
  • 安卓动态控制RelativeLayout的组件

    千次阅读 2015-10-03 16:44:35
    安卓动态控制RelativeLayout的组件的位置以及控件的动态生成
  • 安卓动态添加隐藏碎片简单用法 欢迎扫码关注微信公众号"野心与家" 首先新建两个碎片: <?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android=...
  • 安卓动态加载SO文件点击上方“技术支持联盟”,选择“置顶公众号”第一时间了解程序员大小事儿作者:百香果,一个坚持认为Coding能改变世界的程序猿,喜欢分享,望互相学习指正,博客地址:www.devmajun.com。...
  • 安卓动态权限

    2019-02-15 20:45:09
    最近做的安卓项目需要实现录音等功能,现在的手机基本上都是安卓6.0以上,所以不可避免的要面临设置动态权限,步骤如下: 1、首先在要申请权限的界面定义全局变量的权限数组和请求状态码; private static ...
  • 安卓动态壁纸

    2014-10-28 16:51:52
    本软件可以改变手机动态壁纸,也可以自动换壁纸,也可以手动换壁纸
  • 安卓动态获取权限看这一篇就够了 为大家带来一篇安卓6.0动态获取权限的内容。看完后,你就能够掌握安卓动态获取权限的相关知识。而不是在面试官面前说我会用Rxperssion 等一些类库实现。为什么这样说,因为在之前我...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 4,009
精华内容 1,603
关键字:

安卓动态