pwn | trick
今天学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字段来找到下一块啊….
新的unlink的利用方法(传说中的unlink)
|<-------------------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的方法, 强网杯补题里有说..
unlink
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/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…
资料
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_size
和size
在进入__libc_free
时, 会把传入的mem
用mem2chunk
转为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_bin
的fd
, main_arena -> bins[1]
存的就是unsorted_bin
的bk
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
- 是否fastbin, 看是否有合适大小的fastbin
- 是否smallbin, 看是否有合适大小的fastbin
- 如果没有合适的fastbin和smallbin, 就进行
consolidate fastbin -> unsorted_bin
- 开始大循环
如果…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
- 是否fastbin
- 是否mmap分配的, 如果是就用munmap
- 不然的话, 即使非mmap分配的small或者large, 就放入unsorted_bin
free这里主要是各种检查