pwn | trick

Author Avatar
Aryb1n 1月 20, 2018

今天学pwn…希望能坚持…现在还没有什么trick, 就当一个备忘的板子

ROPGadget里的only

坑爹啊…铁三的时候用这个--only 'int'…只能找到一个int 0x80
然后碰巧这题里…这个gadget最低位是坏字符用不了…
现在才发现,,,,不加这玩意, 随便| grep 'int 0x80'能得到一堆…
伤…
我特么

21字节shellcode

应该是x86通用的?

shellcode  = "\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73"
shellcode += "\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0"
shellcode += "\x0b\xcd\x80"
   0:   31 c9                   xor    ecx,ecx
   2:   f7 e1                   mul    ecx
   4:   51                      push   ecx
   5:   68 2f 2f 73 68          push   0x68732f2f
   a:   68 2f 62 69 6e          push   0x6e69622f
   f:   89 e3                   mov    ebx,esp
  11:   b0 0b                   mov    al,0xb
  13:   cd 80                   int    0x80

更改执行流程

栈溢出, 覆盖返回地址

更改got表, 有的题目是静态链接或者开启full RELRO保护不能写GOT的时候就可以考虑使用malloc_hook

使用malloc_hook, 这个刚刚学到, 在RCTF2017-RNote里, 这题有点神奇, 错位分配fastbin到malloc_hook

emmmm

有个疑问, 就是静态链接到底可以改got表来劫持流程吗…

常识

prev是指低地址的, 我智商太低老是反映不过来
比如, free的时候, 合并到低地址的空闲chunk是如下操作

if (!prev_inuse(p)) {
    prevsize = prev_size(p);
    size += prevsize;
    p = chunk_at_offset(p, -((long) prevsize));
    unlink(av, p, bck, fwd);
}
------------------ <- 低地址
prev_size | size
------------------ 
fd      |    bk
------------------ 
not used content
------------------ <- p
prev_size | size
-----------------
Data
-----------------

变成

------------------ <- p
prev_size | size
------------------ 
fd      |    bk
------------------ 
not used content
------------------ <- 原来p的指向

-----------------

-----------------

利用leave伪造栈帧

push ebp
mov  ebp, esp

leave # mov esp, ebp | pop ebp
ret

这个leave可以改变esp的值, 所以只要伪造一个ebp, 然后跳到一个leave的片段就可以

旧的栈帧
-----------------------------
padding | fake ebp | ret_addr
                        +
                        +--> leave | ret ----------------+
                                +----> esp = fake ebp    |
            +---------------------------+  pop ebp       |
            |                        +---------+         |
            +                        |                   |
伪造的栈帧 esp(原fake ebp)           |     | ret_addr <--+
-------------------------------------+-----+----------
fake ebp2(上一句的leave的后半句要pop ebp)  | fake addr

把这里的ret_addr设置为leave; ret的gadget的地址
当执行leave这一句的时候, 当前的esp就变为了我们设置的fake ebp, 这个时候下一句的ret就是从我们当前的esp处(我们前面的fake的ebp地址)弹出的地址作为返回地址了, 所以我们还要提前在fake ebp这里写好, 这里就需要我们能有一块知道地址并且可写的地址来放我们的fake ebp指向的区域

应该是适用于那种栈溢出能利用的字节比较少的时候, 对…做了几道题, 发现…这原来叫栈迁移…就一般只能覆盖到ebp和ret的时候可以用这个技巧

libc_csu_init处的gadget

可以控制大量的寄存器, 常用于64位rop…然后顺手牵一段代码

MK_FP

一般你看到的MK_FP是用来实现Canary的
ida里类似的代码

v3 = *MK_FP(__FS__, 40LL);
...
v1 = *MK_FP(__FS__, 40LL) ^ v3;

堆方向大概是这样子的

+--------------+ 高
+  top chunk   |
+--------------+
+ size of top  |
+--------------+
+ prev size    |
+--------------+
+--------------+
+ chunk0       |
+--------------+
+size of chunk0|
+--------------+
+ prev size    |
+ -------------+ 低

实际堆块大小

以32为例, malloc(size), 这里是说至少能放得下size大小的data, 那么实际割下来一块((size + 4 + 7) & ~7)
先说这里的 (size + 4) 这一部分, 因为需要8bit的meta信息, 而又可以使用下一块的4bit的prev_size, 所以元信息只要额外的4bit

----------+-----
prev_size | 4bit
----------+-----
size      | 4bit
----------+-----
chunk     | ?bit
----------+-----
人家的prev_size
----------+-----

再说这里的((x + 7) & ~7), 这个是为了8字节对齐, 明眼人一下就看出来这是个分段函数
x = 1...8时候f(x) = 8, x = 9...16的时候f(x) = 16, 恩, 就这样子

另外, 32位以8字节对齐, 那64位应该是16字节对齐的?所以就是((size + 8 + 15) & ~15), ???

对的, 是双字对齐double-word aligned

后来, 在读malloc源码的时候看到了…
具体对齐的代码是

#define request2size(req)                                         \
  (((req) + SIZE_SZ + MALLOC_ALIGN_MASK < MINSIZE)  ?             \
   MINSIZE :                                                      \
   ((req) + SIZE_SZ + MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK)

本来是需要两个额外的size_t来存储头部信息, 但由于可以借用下一个chunk的prev_size, 所以这里只要加一个size_t, 如果是mmap申请的, 就没有办法借用…就需要加2*size_t, 然后再对齐

fast bin 大小

16~64(0x10, 0x40) bytes for x86_32
32~128(0x20, 0x80) bytes for x86_64
这个大小应该指的是data的大小, 还是总共分的大小
这个data是chunk的大小, 不是data的大小

根据上面的计算公式, 这个, 那个32bit下…malloc 大小0x0和0x8的..会得到一个0x10大小的chunk, 也就是最小的chunk

malloc(0);  -> free -> fastbinY[0], 0x10
malloc(8);  -> free -> fastbinY[0], 0x10
malloc(16); -> free -> fastbinY[1], 0x18
malloc(24); -> free -> fastbinY[2], 0x20
malloc(32); -> free -> fastbinY[3], 0x28
malloc(40); -> free -> fastbinY[4], 0x30
malloc(48); -> free -> fastbinY[5], 0x38
malloc(56); -> free -> fastbinY[6], 0x40

实际上这个fastbinY是有10个位置…fastbinY[0..9], 意思是后三个[7..9], 其实是没用上的, 这个可能通过设置来实现?

char * p1 = malloc(12);
char * p2 = malloc(12);
char * p3 = malloc(12);
free(p1);
free(p2);
free(p3);

对应fastbinY差不多…是这样子的

fastbinY[0] -> 'p3' -> 'p2' -> 'p1' -> NULL

等再分配的时候, 会把p3先unlink出去, 然后是p2, p1
相当于是栈, LIFO, 但怎么感觉哪里怪怪的…这个栈顶在fastbinY[?]

而对于普通的bins, 是这样子

bins[0] -> 0x604940 -> 0x604820
           后入后出    先入先出

进入的方向和fastbins是一样的, 但出去的方向是不一样的

普通的bin有很多…大概是…126 这么多…而且bins是双向链表, 一个位要占两个格子

1 unsorted bin
62 small bin
63 large bin

那我怎么感觉, 调试的时候在最后一个bins和binmap时间有8字节的padding呢…

FIFO & LIFO

fastbins是先进后出, 像栈一样
其他的bins都是先进先出, 像队列一样

checksec

FULL RELRO之后就不能改写got表了

hint

UAF 用来leak堆上的信息, 然后计算一些地址

double free用来利用unlink漏洞(以前glibc的unlink没有检查, 只需要一次free就可以利用了) 看到某人写到double free的核心在于第二次free的时候发生的unlink, 虽然叫double free, 但其实只要是free一个指向堆内存的指针都有可能产生可以利用的漏洞, 我觉得看了很多还是这一篇讲述的清楚 http://www.cnblogs.com/elvirangel/p/7203540.html

注意了…bins里的size字段存储的是prev_size + size + data的长度, 不是data的长度, 只有这样子才能通过size字段来找到下一块啊….

|<-------------------p1------------------------->|<-----p2------>
+-----------+------+-----------------------------+----------------+
| prev_size | size |fake_prev|fake_size|fake_data|prev|size|data  |
+-----------+------+-----------------------------+----------------+
                   |<-----fake出来的堆块-------->|<-溢出时fake一下|

通过p1的堆溢出来在p1的数据部分fake一个堆, 同时对p2的下一块的prev(改成fake出来堆块的大小)和size(主要是标志位)进行修改, 让系统以为p1已经空闲, 然后在free(p2)的时候就可以对p1进行unlink, 这个时候就要注意fake出来的堆块的构造方式, 主要是fake_fd和fake_bk….

Unlink有很多中利用方式, 就是有不同的构造方式, 这里是用overflow的方法, 也可以用overlap的方法, 强网杯补题里有说..

double free的经典题目0ctf2015-freenote的wp很多, 但我感觉这一篇真棒, 有图真好, http://angelboy.logdown.com/posts/259180-0ctf-2015-write-up
http://rk700.github.io/2015/04/21/0ctf-freenote/

总觉UAF和double free哪里很像

问题终结…
在某大佬的PPT里说…
Double Free算是一种特殊的UAF

很多天了, 没搞清楚这个unlink的double free

在unlink一个块, 我们叫他p = 0x604800的时候是(64位为例)
用方块代表取一个地址里的东西

BK = p -> bk
   BK = [0x604800 + 0x18] = 0x604900
FD = p -> fd
   FD = [0x604800 + 0x10] = 0x604700
然后
BK -> fd = FD -> bk
   [0x604700 + 0x18] = [0x604900 + 0x10] 
   [0x604900 + 0x10] = [0x604700 + 0x18]

我已经崩坏了….

double link的时候, ptr是找到一个地址指向我们要unlink的块, 而不是 ptr == p. 是*ptr == p,(p是我们unlink的时候要释放的fake出来的块), 理解错误之一
unlink的时候这个会发生一次赋值ptr = p - 0x18, 这里是我理解的最大错误之二, 应该是*ptr = ptr - 3
然后如果能对ptr里存的地址所指向的位置进行写的话, 就可以写到ptr - 3的地方
现在才懂这个图是啥意思 http://www.cnblogs.com/0xJDchen/p/6195919.html
然后对 note[0] -> content (我们上面的ptr)进行写入的话, 其实就是写到了ptr - 0xc这个地址, 因为ptr - 0x18这个地址位于ptr的上方, 所以这样子就能重新覆盖掉ptr, 比如把ptr里的内容覆盖成free@got,
我怕是个傻子

妈的, 终于懂了

贴一篇关于堆, 感觉挺好的

https://paper.seebug.org/255/

格式化字符串

看这个 https://paper.seebug.org/246/
当格式化字符串存储在bss, 堆上的时候怎么办
当格式化字符串使用不同函数接收时候的字符限制
当开启保护不能改got表怎么办 -> 改stdin虚表

有的时候不能想当然

void func1() {
    int a = 2;
    char buffer[8];
    func2();
    ...
}

我看到这个的时候一般认为, 局部变量a应该是先进行分配, 所以处于更高的位置, buffer位于低一些的位置, 更靠近func2的调用堆栈, 其实呢, 编译发现…gcc可能会对局部变量的位置进行调整, 就像这里, 我看到的是 buffer 更高, 所以不能YY, 要自己看编译后的结果…毕竟不知道编译的时候gcc对程序做了什么优化对齐啊什么什么的

这样子一段

setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
return alarm(0x3Cu);

对这个setvbuf还是不太懂
后来…偶然调试了一下…
如果不setbuf的话…在用printf, scanf输入输出的时候会在堆上申请内存来作为输入输出缓冲区
调试发现, 第一次用printf会产生一个0x400(1024)大小的缓冲区, 如果加上setvbuf(stdout, 0, 2, 0)就不会产生这个缓冲区
scanf同理, 也会产生一个
那么如果取消缓冲区的话….那他们怎么缓冲呢QwQ

格式化字符串

test使用AAAA.%1$p.%2$p...这样子的

任意地址读

确定位置之后,比如是%3$p, 使用addr.%3$s来任意地址读…我这里写了个题, 利用格式化字符串来DynELF…结果失败了,QAQ, 不知道为啥

任意地址写

可以用pwntools带的fmtstr_payload…

资料

https://paper.seebug.org/246/

IDA相关

就写在这里吧…
IDA栈帧表示

-0000000000000008                 db ? ; undefined
-0000000000000007                 db ? ; undefined
-0000000000000006                 db ? ; undefined
-0000000000000005                 db ? ; undefined
-0000000000000004 var_4           dd ?
+0000000000000000  s              db 8 dup(?)
+0000000000000008  r              db 8 dup(?)

这个s是saved的ebp, r是返回地址ret

生成器

经常用的

libc = ELF("./libc32")
list(libc.search('/bin/sh'))[0] # 写法一
libc.search('/bin/sh').next() # 写法二

这个libc.search() 返回的就是一个generator, 大概用法

def gen():
    x = 0
    while True:
        yield x
        x += 1
# 用法1
for i in gen():
    do sth...
# 用法1
g = gen()
print g.next()
print g.next()
print g.next()

leak libc

因为fastbin是单链表, 所以要其他bin才能leak出main_area, 从而算出来libc地址

bins[0]

bins[0]貌似是没有意义的…

因为在malloc_init_state中…这样子写道

static void malloc_init_state (mstate av)
{
    int i;
    mbinptr bin;
    /* Establish circular links for normal bins */
    for (i = 1; i < NBINS; ++i)
    {
        bin = bin_at (av, i);
        bin->fd = bin->bk = bin;
    }
    ...

这里的bin_at

/* addressing -- note that bin_at(0) does not exist */
#define bin_at(m, i) \
  (mbinptr) (((char *) &((m)->bins[((i) - 1) * 2]))                              \
             - offsetof (struct malloc_chunk, fd))

喔…不对…我的理解好像出现了偏差…

mem chunk

malloc里把malloc和free的的指针, 称mem, 即不包含chunk的头部的prev_sizesize
在进入__libc_free时, 会把传入的memmem2chunk转为chunk
其中, mem2chunk

#define mem2chunk(mem) ((mchunkptr)((char*)(mem) - 2*SIZE_SZ))

对应的chunk2mem

#define chunk2mem(p)   ((void*)((char*)(p) + 2*SIZE_SZ))

关于初始化

初始化之后…
fastbinsY[0…9] 都为 0
bins 并不是为0, 而是会指向自身, 所以是是可以通过这个leak出main_area的地址

gdb-peda$ p main_arena 
$1 = {
  mutex = 0x0, 
  flags = 0x1, 
  fastbinsY = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, 
  top = 0x804b070, 
  last_remainder = 0x0, 
  bins = {0xf7f977b0 <main_arena+48>, 0xf7f977b0 <main_arena+48>, 
    0xf7f977b8 <main_arena+56>, 0xf7f977b8 <main_arena+56>, 
    0xf7f977c0 <main_arena+64>, 0xf7f977c0 <main_arena+64>, 
    0xf7f977c8 <main_arena+72>, 0xf7f977c8 <main_arena+72>, 
    0xf7f977d0 <main_arena+80>, 0xf7f977d0 <main_arena+80>, 
    ....

真是醉了….
说是unsorted_bin是bin[1]…然而

#define unsorted_chunks(M) (bin_at(M, 1))

#define bin_at(m, i) \
(mbinptr) (((char *) &((m)->bins[((i) - 1) * 2])) \
- offsetof (struct malloc_chunk, fd))

带进去算一下就可以知道…其实是 main_arena -> bins[0] 存的就是unsorted_binfd, main_arena -> bins[1]存的就是unsorted_binbk

gdb里

gdb-peda$ p *(mfastbinptr)0x804b000
$5 = {
  prev_size = 0x0, 
  size = 0x91, 
  fd = 0xf7f97838 <main_arena+184>, 
  bk = 0x804b0a0, 
  fd_nextsize = 0x41414141, 
  bk_nextsize = 0x41414141
}

malloc & free

_int_malloc

  1. 是否fastbin, 看是否有合适大小的fastbin
  2. 是否smallbin, 看是否有合适大小的fastbin
  3. 如果没有合适的fastbin和smallbin, 就进行consolidate fastbin -> unsorted_bin
  4. 开始大循环

如果…unsorted_bin中只有一个last_remainder.并且大小足够分, 就把这一块分一半给用户, 剩下的一块就是新的last_remainder, 再重新放入unsorted_bin

大循环:
反向寻找unsorted_bin, 最多找1w次…
大小精确相等的, 就可以直接分配
如果当前块是small bins, 就放到对应大小的smallbin的第一个位置
如果是large bins, 就放到largebin的合适位置(因为large是一个范围, 大小排序好的)

所以…small_bin和large_bin都是从unsorted_bin取出来放进去的…

明天再写…这个循环有点迷

_int_free

  1. 是否fastbin
  2. 是否mmap分配的, 如果是就用munmap
  3. 不然的话, 即使非mmap分配的small或者large, 就放入unsorted_bin

free这里主要是各种检查