程序

gdb

  • []表示直接输入

  • “”表示需由引号包裹

  • {}表示键盘操作

基本

  • help [指令]查看个指令的用法

  • i即info 查看信息

    • i b查看断点
    • i r查看寄存器
    • i f查看函数名
  • show与info类似,但更多是调试信息
  • stack [数]查看栈
  • backtrace查看当前调试环境中所有栈帧的信息,栈回溯.
  • vmmap内存段基本信息
  • frame查看当前frame(函数,栈帧)里的变量值等信息
  • display /[n]i $pc查看当前往下n行代码
  • disassemble *addr反汇编某块内存,disassemble /r带机器码
  • distance计算距离
  • context(ctx)再次打印当前状态

随机化信息

  • libc打印libc基址
  • ld打印ld基址
  • code打印code基址
  • tls打印tls地址
  • heapbase打印heap基址

符号与源码

  • info sharedlibrary 查询符号表地址

  • add-symbol-file /home/aichch/glibc-all-in-one/libs/2.31-0ubuntu9_amd64/.debug/libc-2.31.so加载符号文件,调试会有一些问题

  • dir查看源码目录

  • dir 指定目录指定查找源码目录,例如调试malloc源码


最后还有一种最有效的加载符号表方式

利用gdb的python api导入调试文件,稳定性最高,调试不出错

借助其api写成的loadsym.py实现

1
2
3
gdb.attach(sh,'''source ./libcdbg/loadsym.py
loadsym /home/aichch/glibc-all-in-one/libs/2.31-0ubuntu9_amd64/.debug/libc-2.31.so
''')

在2.34以前可以直接加载.debug目录下的对应符号文件,如以上所示

不过从2.34开始,提供的glibc符号文件格式发生变化,loadsym的对象也有一些变化

现在我们需要先file查看下载的libc.so的build-id,例如2.36-0ubuntu4_amd64的build-id是d1704d25fbbb72fa95d517b883131828c0883fe9,那么就前往.built-id文件夹寻找d1文件夹,找到其中的704d25fbbb72fa95d517b883131828c0883fe9.debug文件,将其加载

1
2
3
gdb.attach(sh,'''source ./libcdbg/loadsym.py
loadsym /home/aichch/glibc-all-in-one/libs/2.36-0ubuntu4_amd64/.debug/.build-id/d1/704d25fbbb72fa95d517b883131828c0883fe9.debug
''')

一般来说,ubuntu的同一个glibc版本的各个小版本的libc.so的符号差距不会太大,因此每个glibc版本有一个调试文件即可

不过特殊情况可能会出现符号较大差异,依然要调试的话可以下载指定版本的libc

执行

  • start开始或重新开始
  • s [数]单步步入,会进入子函数,数有填的话就是执行多少步,源码层面
    • si [数]同上,汇编层面
  • n [数]单步步过,不会进入子函数,数有填的话就是执行多少步,源码层面
    • ni [数]同上,汇编层面
  • c继续执行到断点,没断点就一直执行下去
  • {ctrl+c}程序输入时强行中断
  • r重新开始执行
  • finish(fi)结束当前正在执行的函数,并在跳出函数后暂停程序的执行
  • return [值]结束当前调用函数并返回指定值,到上一层函数调用处停止程序执行。
  • jump(j) [地址]使程序从当前要执行的代码处,直接跳转到指定位置处继续执行后续的代码。即跳过部分代码
  • until(u)单纯使用 until 命令,可以运行程序直到退出循环体。
    • until n命令中,n 为某一行代码的行号,该命令会使程序运行至第 n 行代码处停止。

断点

  • b *[地址]
    • b *$ rebase([相对偏移])开启pie时可用
  • b [函数名]
  • b [数]源码第几行断点
  • b +[数]当前往下多少停住,同样可以有-
  • delete [数]删除断点
  • disable [数]禁用断点
  • enable [数]启用断点
  • clear清楚下面所有断点
  • watch [地址]该地址数据改变时停下,例如watch * (long long int*)0xdeadbeef或者watch {long long int}0xdeadbeef
  • watch [变量]该变量改变的时候会断
  • awatch [地址]硬件断点,访问时便会中断
  • info watchpoints 查看watch断点信息
  • catch syscall syscall系统调用的时候断住,后面可以跟指定的系统调用号
  • tcatch syscall syscall系统调用的时候断住,只断一次
  • info break catch的断点可以通过i b查看

查找数据

  1. search [值(多是字面值,字符串之类)]
  2. find "字符串" pwndbg独有

查看内存

  • x /[nuf]

    • n代表几个单元

    • u代表每个单元几个字节(b—1|h—2|w—4|g—8)

    • f代表显示格式

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      x 按十六进制格式显示变量。
      d 按十进制格式显示变量。
      u 按十六进制格式显示无符号整型。
      o 按八进制格式显示变量。
      t 按二进制格式显示变量。
      a 按十六进制格式显示变量。
      c 按字符格式显示变量。
      f 按浮点数格式显示变量。
      s 按字符串显示。
      b 按字符显示。
      i 显示汇编指令
  • print(p) (&)[符号]打印符号的内容或查看符号的地址,支持进一步的指针调用,例如p _rtld_global->_dl_ns->_ns_loaded->l_info[26]

    • p (*(类型\*)内存地址),可以将某一块内存视为指定符号打印内容,例如p (*(struct _IO_FILE_plus*)0x603180)
  • telescope [内存|符号]查看内容以更直观的方式

  • ptype [符号]打印符号定义例如ptype struct _IO_wide_data

    • ptype /o额外显示内部成员偏移
  • hexdump像debug那样显示内存

  • dump memory bin start enddump内存

修改数据

  • set $[寄存器]=[值] 把寄存器的值变为所更改的
  • set *([地址])=[值] 地址的值更改,注意带星号,默认int位数,例如set * (long long int*)0xdeadbeef=0x123456789
  • set {数据类型}([地址])=[值]地址的值修改(指定位数).不用星号.例如set {long long int}0xdeadbeef=0x123456789
  • set args "a1" "a2" "a3" 给参数123赋值
  • set args "python3 -c 'print "1234\x7f\xde"'"' 使用python给参数赋值不可见字符

多线程

  • info threads查看可切换调试的线程:
  • thread 线程id切换调试的线程
  • set scheduler-locking on只运行当前线程
  • set scheduler-locking off运行全部的线程
  • thread apply 线程id gdb_cmd指定某线程执行某gdb命令
  • thread apply all gdb_cmd全部的线程执行某gdb命令

堆(pwndbg)独有

  • mp查看mmap详细信息,包括临界信息
  • top_chunk显示topchunk信息

  • arena 显示当前arena的详细信息

    • arenas 显示所有arena的基本信息
    • arenainfo 好看的显示所有arena的信息
  • bins

    常用,查看所有种类的堆块的链表情况

    • fastbins 单独查看fastbins的链表情况
    • largebins 同上,单独查看largebins的链表情况
    • smallbins 同上,单独查看smallbins的链表情况
    • unsortedbin 同上,单独查看unsortedbin链表情况
    • tcachebins 同上,单独查看tcachebins的链表情况
    • tcache 查看tcache详细信息
  • heap

    数据结构的形式显示所有堆块,会显示一大堆

    • heapbase 查看堆起始地址
    • heapinfoheapinfoall 显示堆得信息,和bins的挺像的,没bins好用
    • parseheap 显示堆结构,很好用
  • tracemalloc 好用,会跟提示所有操作堆的地方

  • malloc_chunk [chunk符号或地址]查看该chunk的信息

  • find_fake_fast [地址] [大小] 查找一块可以在给定地址附近伪造fake_fast_chunk的内存

  • vis以数据块模式查看堆

其他(一些是pwndbg独有)

  • cyclic [数] 生成用来溢出的字符
    • cyclic -l [ 地址]搭配使用
  • $reabse //开启PIE的情况的地址偏移
    • b *$reabse(0x123456) 断住PIE状态下的二进制文件中0x123456的地方
    • codebase 打印PIE偏移,与rebase不同,这是打印,rebase是使用
  • retaddr 打印包含返回地址的栈地址
  • canary 直接看canary的值
  • plt 查看plt表
  • got 查看got表
  • hexdump [地址] [数] 像IDA那样显示数据,带字符串
  • magic打印一些useful符号

python-API

文档Python API (Debugging with GDB)

在gdb命令行中,直接python $gdb-function即可使用

善用help [命令]

gdb-multiarch

target remote : $port连接调试端口

set architecture $arch设置调试架构

set endian little/big设置大小端

gdb-multiarch启动的时候最好带上本地文件

调试异构时无法使用ctrl+c发送系统中断,因此只能打断点使程序停下

gdb-multiarch在程序崩溃时,偶尔会产生占用巨大空间的coredump文件

可以进入/var/lib/apport/coredump文件夹将其删除

qemu

qemu-$arch -g $port -L $sharelib $fileqemu启动程序

one_gadget

one_gadget是libc中存在的一些执行execve("/bin/sh", NULL, NULL)的片段,当可以泄露libc地址,并且可以知道libc版本的时候,可以使用此方法来快速控制指令寄存器开启shell。

相比于system("/bin/sh"),这种方式更加方便,不用控制RDI、RSI、RDX等参数。运用于不利构造参数的情况。

1
one_gadget <FILE|-b BuildID> [options]
  1. 如果是使用_malloc_hook来调用one_gadget,那么需要配合realloc来构造所需参数,realloc在libc中的符号是__libc_realloc
  2. 如果是使用其他方式调用one_gadget,比如说修改GOT表,那么需要在栈上提前构造好参数,或者将rax寄存器清零

ROPgadget

ROPgadget --binary filename --only 'pop|ret'搜索gadget

ROPgadget --binary filename --string '/bin/sh'字符串

还有其他不少功能,详见help

ropper

可以先使用ropper直接进入程序内部

再使用其他命令

gadget

亦或者直接

ropper --file filename --string '/bin/sh'

ropper --file filename --search 'asm_code'

指令可以指搜索部分例如mov rdx,也可以搜索更全mov rdx, rdi亦或者多条mov rdx, rdi; mov rdi, rax;要注意空格,逗号,分号的位置,不然搜不到

?和%的用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[Search]
./Ropper.py --file /bin/ls --search <searchstring>
? any character
% any string

Example:

./Ropper.py --file /bin/ls --search "mov e?x"
0x000067f1: mov edx, dword ptr [ebp + 0x14]; mov dword ptr [esp], edx; call eax
0x00006d03: mov eax, esi; pop ebx; pop esi; pop edi; pop ebp; ret ;
0x00006d6f: mov ebx, esi; mov esi, dword ptr [esp + 0x18]; add esp, 0x1c; ret ;
0x000076f8: mov eax, dword ptr [eax]; mov byte ptr [eax + edx], 0; add esp, 0x18; pop ebx; ret ;

./Ropper.py --file /bin/ls --search "mov [%], edx"
0x000067ed: mov dword ptr [esp + 4], edx; mov edx, dword ptr [ebp + 0x14]; mov dword ptr [esp], edx; call eax;
0x00006f4e: mov dword ptr [ecx + 0x14], edx; add esp, 0x2c; pop ebx; pop esi; pop edi; pop ebp; ret ;
0x000084b8: mov dword ptr [eax], edx; ret ;
0x00008d9b: mov dword ptr [eax], edx; add esp, 0x18; pop ebx; ret ;

./Ropper.py --file /bin/ls --search "mov [%], edx" --quality 1
0x000084b8: mov dword ptr [eax], edx; ret ;; ret ;

感觉比ROPgadget要好用一点

其他

ropper还附带许多功能,例如asm和disasm等,不过用得比较多的还是gadget

更多详见说明文档

binutils

1
2
3
4
5
6
readelf 读取基本信息
file 读取基本信息
strings 读取文件字符串
ldd 查看文件使用的libc库和ld链接器
objdump 反汇编指令
nm 列出符号表
工具 功能
strace 跟踪程序执行过程中产生的系统调用及接收到的信号
readelf 用于查看ELF格式的文件信息
file 用于辨识文件类型
objdump 以一种可阅读的格式让你更多地了解二进制文件可能带有的附加信息
ldd 列出一个程序所需要得动态链接库
hexdump hexdump主要用来查看“二进制”文件的十六进制编码
ar 创建静态库,插入/删除/列出/提取 成员函数
strings 列出目标文件中所有可打印的字符串
nm 列出目标文件中符号表所定义的符号
strip 从目标文件中删除符号表的信息
size 列出目标文件中各个段的大小

patchelf

ldd查看libc和ld路径(file和readelf指令也行)

1
2
3
4
linux:~/pwn$ ldd elf
linux-vdso.so.1 (0x00007fffd31d7000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007efc8b627000)
/lib64/ld-linux-x86-64.so.2 (0x00007efc8b834000)

patchelf使用

ld切换

1
patchelf --set-interpreter /home/aichch/glibc-all-in-one/libs/2.31-0ubuntu9_amd64/ld-2.31.so[绝对路径] ./elf

libc切换

1
patchelf --replace-needed libc.so.6[符号] /home/aichch/glibc-all-in-one/libs/2.31-0ubuntu9_amd64/libc-2.31.so[绝对路径] ./elf

也有这样修改libc的

1
patchelf --set-rpath /home/aichch/glibc-all-in-one/libs/2.31-0ubuntu9_amd64/libc-2.31.so[绝对路径] ./elf

patchelf --set-rpath 的作用是 在ELF文件的 Dynamic section 的中添加一个 RUNPATH 段里面储存着动态链接库的绝对路径,即设置程序的动态链接库

readelf -d ./elf 查看runpath和needed

ld版本与libc版本要对应

不过有时替换了之后,ldd会显示不是可执行文件,但却可以正常运行

glibc_all_one

更新

./update_list

下载

./download libc_name

其他来源glibc安装

glibc-2.23为例

解压
tar xvf glibc-2.23.tar.gz

进入解压目录

1
2
3
cd glibc-2.23
mkdir build
cd build

选择带 debug symobl 以及配置好安装位置

CFLAGS="-g -g3 -ggdb -gdwarf-4 -Og -w" CXXFLAGS="-g -g3 -ggdb -gdwarf-4 -Og -w" ../configure --prefix=/home/aichch/glibc_all_one/libs/glibc-2.23/

CFLAGS、CXXFLAGS 与 debug symobl 有关

—prefix 是安装目录

编译安装

make && make install

若出现

1
2
3
/tmp/ccPRCqlU.s: Error: `loc1@GLIBC_2.2.5' can't be versioned to common symbol 'loc1'
/tmp/ccPRCqlU.s: Error: `loc2@GLIBC_2.2.5' can't be versioned to common symbol 'loc2'
/tmp/ccPRCqlU.s: Error: `locs@GLIBC_2.2.5' can't be versioned to common symbol 'locs'

解决方案就是在 /misc/regexp.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
diff --git a/misc/regexp.c b/misc/regexp.c
index 19d76c0..9017bc1 100644
--- a/misc/regexp.c
+++ b/misc/regexp.c
@@ -29,14 +29,17 @@

#if SHLIB_COMPAT (libc, GLIBC_2_0, GLIBC_2_23)

-/* Define the variables used for the interface. */
-char *loc1;
-char *loc2;
+#include <stdlib.h> /* Get NULL. */
+
+/* Define the variables used for the interface. Avoid .symver on common
+ symbol, which just creates a new common symbol, not an alias. */
+char *loc1 = NULL;
+char *loc2 = NULL;
compat_symbol (libc, loc1, loc1, GLIBC_2_0);
compat_symbol (libc, loc2, loc2, GLIBC_2_0);

/* Although we do not support the use we define this variable as well. */
-char *locs;
+char *locs = NULL;
compat_symbol (libc, locs, locs, GLIBC_2_0);

dd

dd 命令用于读取转换输出数据。

  • if=文件名:输入文件名,默认为标准输入。即指定源文件。
  • of=文件名:输出文件名,默认为标准输出。即指定目的文件。
  • ibs=bytes:一次读入bytes个字节,即指定一个块大小为bytes个字节。
    obs=bytes:一次输出bytes个字节,即指定一个块大小为bytes个字节。
    bs=bytes:同时设置读入/输出的块大小为bytes个字节。
  • cbs=bytes:一次转换bytes个字节,即指定转换缓冲区大小。
  • skip=blocks:从输入文件开头跳过blocks个块后再开始复制。
  • seek=blocks:从输出文件开头跳过blocks个块后再开始复制。
  • count=blocks:仅拷贝blocks个块,块大小等于ibs指定的字节数。
  • conv=<关键字>,关键字可以有以下11种:
    • conversion:用指定的参数转换文件。
    • ascii:转换ebcdic为ascii
    • ebcdic:转换ascii为ebcdic
    • ibm:转换ascii为alternate ebcdic
    • block:把每一行转换为长度为cbs,不足部分用空格填充
    • unblock:使每一行的长度都为cbs,不足部分用空格填充
    • lcase:把大写字符转换为小写字符
    • ucase:把小写字符转换为大写字符
    • swap:交换输入的每对字节
    • noerror:出错时不停止
    • notrunc:不截短输出文件
    • sync:将每个输入块填充到ibs个字节,不足部分用空(NUL)字符补齐

xxd

对数据进行十六进制存储

参数

  • -ps:以纯十六进制形式打印
  • -c[]:显示多少列
  • -s:start at \ bytes abs. (or +: rel.) infile offset.

diff

用于对比两个文件之间的差异

也可以用于对比两个文件夹,使用-r进行递归

python包

pwntools

详细见pwntools —documentation

程序启动

process启动

强制使用给定的ld和libc启动进程

1
p = process(argv=["/home/aichch/glibc-all-in-one/libs/2.31-0ubuntu9_amd64/ld-2.31.so", "./elf"],env={"LD_PRELOAD":"/home/aichch/pwn/libc-2.31.so"})

aslr等级设置

1
p=process('./elf',aslr=0/1/2)

带参数启动

1
p=process(argv=['./elf',arguments])

debug启动

1
io = gdb.debug("/bin/bash", gdbscript='continue')

在这个例子中,传入了gdbscript='continue',以使调试器恢复执行,但是可以传入任何有效的 GDB 脚本命令,它们将在调试进程启动时被执行。

运行异构架构的进程必须用gdb.debug启动,以便对其进行调试

以debug()启动的程序会停在start函数的起始,即程序的最开始

remote远程

1
p=remote(ip,host,ssl=false)

参数:

1
2
3
4
5
6
7
8
9
10
11
Arguments:
host(str): The host to connect to.
port(int): The port to connect to.
fam: The string "any", "ipv4" or "ipv6" or an integer to pass to socket.getaddrinfo.
typ: The string "tcp" or "udp" or an integer to pass to socket.getaddrinfo.
timeout: A positive number, None or the string "default".
ssl(bool): Wrap the socket with SSL
ssl_context(ssl.SSLContext): Specify SSLContext used to wrap the socket.
sni: Set 'server_hostname' in ssl_args based on the host parameter.
sock(socket.socket): Socket to inherit, rather than connecting
ssl_args(dict): Pass ssl.wrap_socket named arguments in a dictionary.

ssh连接

shell = ssh(user=user, host=host, port=port, password=password)

数据接收与发送

1
2
3
4
5
6
7
8
9
10
11
sh.recv(numb = 2048, timeout = dufault)  接受数据,numb指定接收的字节,timeout指定超时
sh.recvline(keepends=True) 接受一行数据,keepends为是否保留行尾的\n
sh.recvuntil(b"Hello,World\n",drop=fasle) 接受数据直到设置的标志出现,drop表示是否接收标志,默认接收
sh.recvall() 一直接收直到EOF
sh.recvrepeat(timeout = default) 持续接受直到EOF或timeout
sh.interactive() 直接进行交互,相当于回到shell的模式,在取得shell之后使用
send(data) - 发送数据
sendline(line) - 发送数据加一个换行
u64()/u32() 解包
p64()/p32() 打包
flat() 平坦化处理数据,会自动小端序但需要指定context.arch,否则默认4字节

针对elf的处理

常用符号处理

1
2
3
4
5
6
7
8
9
10
11
12
elf=ELF('libc.so.6')

elf.symbols['symbol']#获取符号,也可以elf.sym['symbol']

elf.plt['func']
elf.got['func']

#search
next(elf.search(b'bytes'))#在程序中搜索字节数据,返回一个迭代器,所以需要next方法获得值
next(elf.search(asm('jmp esp'), executable = True))#虽然一般用来查找字符串,但其实能查找所有的字节数据,例如机器码

elf.address=addr#指定基址

更多

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#read,write and patch

e = ELF('/bin/cat')
e.read(e.address+1, 3)
b'ELF'
e.asm(e.address, 'ret')
e.save('/tmp/quiet-cat')
disasm(open('/tmp/quiet-cat','rb').read(1))
' 0: c3 ret'

#get section
elf.get_section_by_name('.dynstr').data()
elf.get_section_by_name('.dynstr').header
elf.get_section_by_name('.dynstr').header.sh_addr

context及log

1
2
3
4
5
6
context.log_level = 'DEBUG'
context(arch='amd64', os='linux')
context(os='linux', arch='amd64', log_level='debug')
context.binary=''根据二进制文件自动获取环境变量
context.terminal('gnome-terminal','-x','sh','-c')新终端参数
log.success("") 打印信息

gdb

1
2
3
4
5
gdb.attach()
可以加入一些命令,例如gdb.attach(p,'b system')

debug_shellcode()
--Build a binary with the provided shellcode, and start it under a debugger
1
2
3
4
5
6
7
Linux 系统有一个叫做trace_scope的设置,它可以阻止非子进程的进程被调试。Pwntools 对于它自己启动的任何进程都能解决这个问题,但是如果你必须在 Pwntools 之外启动一个进程,并试图通过 pid 附加到它(例如gdb.attach(1)),可能被阻止附加。

可以通过禁用安全设置和重启机器来解决这个问题:

sudo tee /etc/sysctl.d/10-ptrace.conf <<EOF
kernel.yama.ptrace_scope = 0
EOF

ROP

1
2
3
4
5
6
7
8
9
10
rop=ROP('')  创建一个rop对象
rop.raw() 填充padding
rop.call('函数',[arg1,arg2,arg3]) 调用函数
#对一些常见函数也可以直接
#rop.read(arg1,arg2,arg3)
#rop.write(arg1,arg2,arg3)
rop.unresolve('') 返回符号的地址
rop.chain() 返回完整的rop链
rop.migrate(base_stage) 将程序流程转移到 base_stage(地址),栈迁移?
rop.dump() 直观地展示当前的 rop 链

pwntools comandline

1
2
checksec 检查保护
ROPgadget gadget查找

qemu

需要用pwntools调试qemu启动的程序,首先需要设置context,不然很多设置会出错

对于动态链接的程序还需要

如果用process()启动异构elf,pwntools只会尝试盲目打开程序,如果失败则会使用qemu逐个尝试打开程序,当然也可以自己指定

例如

  1. p=process(["qemu-arm", "-g", "1234", "-L", "/usr/arm-linux-gnueabihf", "./armup_buu"])

    不过这样启动就是调试模式,程序完全由gdb-multiarch控制

  2. p=process(["qemu-arm", "-L", "/usr/arm-linux-gnueabihf", "./armup_buu"])

    这样启动程序就能正常向下执行了

只是这样打开的话gdb.attach(p)是附加不上去的,只能到另一个命令行使用gdb.multiarch调试


当然也可以通过gdb.debug()打开异构elf,pwntools会自动添加适合的命令行参数给qemu,并正确启动gdb

不过实际发现上面两种方式在调试时都不是很灵敏

constants

constants可以返回诸多c源文件中定义的常量

LibcSearcher

1
2
3
4
5
libc = LibcSearcher("函数名",函数真实地址)
libc.add_condition(leaked_func, leaked_address) #增加限制提高准确度
libcbase = 函数真实地址 – libc.dump("函数名")
system_addr = libcbase + libc.dump("system") #system 偏移
bin_sh_addr = libcbase + libc.dump("str_bin_sh") #/bin/sh 偏移

另外如果是pypi上下载的更新版本,还多了一些功能

1
2
3
4
5
6
7
8
9
len(libc) # 返回在当前约束条件下,可能的 Libc 数量

print(libc) # 若 Libc 已被唯一确定,打印其详细信息

for obj in libc :
print(obj) # 实现了迭代器,打印(或其它操作)当前所有可能的 Libc

obj.select_libc() # 打印可能的 Libc 列表,手动选择一个认为正确的 Libc
obj.select_libc(2) # 手动选择 2 号 Libc 作为正确的 Libc

struct

将字节串解读为打包的二进制数据,可以方便地调整字节序,大小和对齐方式以及进行格式化

struct.pack(format, v1, v2, )

返回一个 bytes 对象,其中包含根据格式字符串 format 打包的值 v1, v2, … 参数个数必须与格式字符串所要求的值完全匹配。

struct.pack_into(format, buffer, offset, v1, v2, )

根据格式字符串 format 打包 v1, v2, … 等值并将打包的字节串写入可写缓冲区 bufferoffset 开始的位置。 请注意 offset 是必需的参数。

struct.unpack(format, buffer)

根据格式字符串 format 从缓冲区 buffer 解包(假定是由 pack(format, ...) 打包)。 结果为一个元组,即使其只包含一个条目。 缓冲区的字节大小必须匹配格式所要求的大小,至少为calcsize() 显示的格式所要求的大小。

struct.unpack_from(format, /, buffer, offset=0)

buffer 从位置 offset 开始根据格式字符串 format 进行解包。 结果为一个元组,即使其中只包含一个条目。 缓冲区的字节大小从位置 offset 开始必须至少为 calcsize() 显示的格式所要求的大小。

struct.iter_unpack(format, buffer)

根据格式字符串 format 以迭代方式从缓冲区 buffer 解包。 此函数返回一个迭代器,它将从缓冲区读取相同大小的块直至其内容全部耗尽。 缓冲区的字节大小必须整数倍于格式所要求的大小,如 calcsize() 所示。

每次迭代将产生一个如格式字符串所指定的元组。

struct.calcsize(format),计算最终大小

字节顺序,大小和对齐方式

字符 字节顺序 大小 对齐方式
@ 按原字节 按原字节 按原字节
= 按原字节 标准
< 小端 标准
> 大端 标准
! 网络(=大端) 标准

请注意 '@''=' 之间的区别:两个都使用本机字节顺序,但后者的大小和对齐方式是标准化的

格式字符

格式字符具有以下含义;C 和 Python 值之间的按其指定类型的转换应当是相当明显的。 ‘标准大小’列是指当使用标准大小时以字节表示的已打包值大小;也就是当格式字符串以 '<', '>', '!''=' 之一开头的情况。 当使用本机大小时,已打包值的大小取决于具体的平台。

格式 C 类型 Python 类型 标准大小
x 填充字节
c char 长度为 1 的字节串 1
b signed char 整数 1
B unsigned char 整数 1
? _Bool bool 1
h short 整数 2
H unsigned short 整数 2
i int 整数 4
I unsigned int 整数 4
l long 整数 4
L unsigned long 整数 4
q long long 整数 8
Q unsigned long long 整数 8
n ssize_t 整数
N size_t 整数
e (6) float 2
f float float 4
d double float 8
s char[] 字节串
p char[] 字节串
P void* 整数

例子

1
2
3
4
5
6
7
8
9
10
11
12
import struct

# 将整数和浮点数打包成二进制格式
bytes1 = struct.pack('i2f', 42, 3.14, 2.718)
print("打包后的二进制数据:", bytes1)

# 将字符串和整数打包成二进制格式
bytes2 = struct.pack('<10si', b'Hello', 123)
print("打包后的二进制数据:", bytes2)

out1 = struct.unpack('i2f', bytes1)
out2 = struct.unpack('<10si',bytes2)

binascii

二进制和 ASCII 码互转

hex

binascii.b2a_hex(data[, sep[, bytes_per_sep=1]])

binascii.hexlify(data[, sep[, bytes_per_sep=1]])

返回二进制数据 data 的十六进制表示形式。 data 的每个字节都被转换为相应的2位十六进制表示形式。因此返回的字节对象的长度是 data 的两倍。

使用bytes.hex()方法也可以方便地实现相似的功能(但仅返回文本字符串)。

如果指定了 sep,它必须为单字符 str 或 bytes 对象。 它将被插入每个 bytes_per_sep 输入字节之后。 分隔符位置默认从输出的右端开始计数,如果你希望从左端开始计数,请提供一个负的 bytes_per_sep 值。

1
2
3
4
>>> hexlify(b'H\x8d\x85\xf0\xf7\xff\xff\xba\x00\x02')
b'488d85f0f7ffffba0002'
>>> hexlify(b'H\x8d\x85\xf0\xf7\xff\xff\xba\x00\x02','_',-2)
b'488d_85f0_f7ff_ffba_0002'

binascii.a2b_hex(hexstr)

binascii.unhexlify(hexstr)

与上面相反

返回由十六进制字符串 hexstr 表示的二进制数据。此函数功能与 b2a_hex()相反。 hexstr 必须包含偶数个十六进制数字(可以是大写或小写),否则会引发 Error 异常。

使用bytes.fromhex()类方法也实现相似的功能(仅接受文本字符串参数,不限制其中的空白字符)。

base64

binascii.a2b_base64(string)

将 base64 数据块转换成二进制并以二进制数据形式返回。一次可以传递多行数据。

binascii.b2a_base64(data, *, newline=True)

将二进制数据转换为一行用 base64 编码的ASCII字符串。返回值是转换后的行数据,如果 newline 为true,则返回值包括换行符。该函数的输出符合:rfc:3548。

requests

requests是基于python的一个HTTP库,封装了许多针对HTTP协议的操作,可以方便的进行网页操作

requests支持的方法

方法 描述
delete(url, args) 发送 DELETE 请求到指定 url
get(url, params, args) 发送 GET 请求到指定 url
head(url, args) 发送 HEAD 请求到指定 url
patch(url, data, args) 发送 PATCH 请求到指定 url
post(url, data, json, args) 发送 POST 请求到指定 url
put(url, data, args) 发送 PUT 请求到指定 url
request(method, url, args) 向指定的 url 发送指定的请求方法

设置请求头headers

1
2
3
4
headers = {
"A" : "B",
"C" : "D"
}

设置data

1
2
3
4
data = {
"name":"AAA",
"age":"22"
}

设置查询参数

1
2
3
4
params = {
'key1': 'value1',
'key2': 'value2'
}

get()和post()等请求方法

1
2
response = requests.post(url, headers=headers, params=params, data=data)
response = requests.get(url, headers=headers, params=params, data=data)

返回值具有以下属性

1
2
3
status_code#返回状态码
text#查看响应内容,response.text 返回的是Unicode格式的数据
url#完整url地址

string

string提供不少方便的字符集分类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
whitespace -- a string containing all ASCII whitespace
ascii_lowercase -- a string containing all ASCII lowercase letters
ascii_uppercase -- a string containing all ASCII uppercase letters
ascii_letters -- a string containing all ASCII letters
digits -- a string containing all ASCII decimal digits
hexdigits -- a string containing all ASCII hexadecimal digits
octdigits -- a string containing all ASCII octal digits
punctuation -- a string containing all ASCII punctuation characters
printable -- a string containing all ASCII characters considered printable


# Some strings for ctype-style character classification
whitespace = ' \t\n\r\v\f'
ascii_lowercase = 'abcdefghijklmnopqrstuvwxyz'
ascii_uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
ascii_letters = ascii_lowercase + ascii_uppercase
digits = '0123456789'
hexdigits = digits + 'abcdef' + 'ABCDEF'
octdigits = '01234567'
punctuation = r"""!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~"""
printable = digits + ascii_letters + punctuation + whitespace

以及字符串模板

1
2
3
4
5
6
7
8
9
from string import Template

# 创建一个简单的字符串模板
template = Template("Hello, $name! Today is $day.")

# 替换模板中的变量
result = template.substitute(name="Alice", day="Monday")

print(result)

还有一些用的不多的方法

hashlib

proof of work或者一些其他场景中会出现

主要的功能是计算sha256以及md5

1
2
md5_hash = hashlib.md5(data.encode()).hexdigest()
sha256_hash = hashlib.sha256(data.encode()).hexdigest()

random

用于快速生成随机数,常用几个方法

1
2
3
4
5
6
7
8
9
10
random.randint(x,y)
#生成x到y的随机整数
random.shuffle(my_list)
#随机打乱序列
random_choice = random.choice(['apple', 'banana', 'orange', 'grape'])
#从序列中随机选择元素
random_letters = ''.join(random.choices('abcdefghijklmnopqrstuvwxyz', k=10))
#从序列中选取多个元素
random.sample(my_list, 3)
#从序列中随机选择指定数量的唯一元素的函数

re

python的正则表达式方法包

re.search(pattern, string, flags=0)

在字符串中搜索匹配指定的正则表达式模式,返回一个匹配对象,如果找到则返回第一个匹配项,否则返回 None

1
2
3
4
5
6
7
8
import re

# 在字符串中搜索匹配指定的正则表达式模式
result = re.search(r'\d+', 'The price is $100')
if result:
print("找到匹配:", result.group()) # 输出: 100
else:
print("未找到匹配")

re.match(pattern, string, flags=0)

尝试从字符串的起始位置匹配正则表达式模式,如果成功匹配则返回一个匹配对象,否则返回 None

1
2
3
4
5
6
7
8
import re

# 尝试从字符串的起始位置匹配正则表达式模式
result = re.match(r'\w+', 'Hello, world!')
if result:
print("找到匹配:", result.group()) # 输出: Hello
else:
print("未找到匹配")

re.findall(pattern, string, flags=0)

在字符串中查找所有匹配指定的正则表达式模式的子串,并以列表形式返回。

1
2
3
4
5
import re

# 在字符串中查找所有匹配指定的正则表达式模式的子串
matches = re.findall(r'\d+', 'There are 10 apples and 20 oranges')
print("所有匹配:", matches) # 输出: ['10', '20']

re.sub(pattern, repl, string, count=0, flags=0)

使用指定的替换字符串(repl)替换字符串中所有匹配指定的正则表达式模式的子串,并返回替换后的字符串。

1
2
3
4
5
import re

# 使用指定的替换字符串替换字符串中所有匹配的子串
new_string = re.sub(r'\d+', 'X', 'There are 10 apples and 20 oranges')
print("替换后的字符串:", new_string) # 输出: There are X apples and X oranges

re.split(pattern, string, maxsplit=0, flags=0)

根据指定的正则表达式模式对字符串进行拆分,并返回拆分后的子串列表。

1
2
3
4
5
import re

# 根据指定的正则表达式模式对字符串进行拆分
parts = re.split(r'\s+', 'Split this string by spaces')
print("拆分结果:", parts) # 输出: ['Split', 'this', 'string', 'by', 'spaces']

re.compile(pattern, flags=0)

将正则表达式模式编译成一个可重复使用的正则表达式对象,可以提高匹配效率。

1
2
3
4
5
6
7
8
9
import re

# 编译正则表达式模式成一个可重复使用的对象
pattern = re.compile(r'\d+')
result = pattern.search('The price is $100')
if result:
print("找到匹配:", result.group()) # 输出: 100
else:
print("未找到匹配")

re.finditer(pattern, string, flags=0)

在字符串中查找所有匹配指定的正则表达式模式的子串,并返回一个迭代器,每个元素是一个匹配对象。

1
2
3
4
5
6
import re

# 在字符串中查找所有匹配指定的正则表达式模式的子串,并返回迭代器
matches = re.finditer(r'\d+', 'There are 10 apples and 20 oranges')
for match in matches:
print("匹配:", match.group()) # 输出: 10, 20

re.fullmatch(pattern, string, flags=0)

尝试完全匹配字符串与正则表达式模式,如果匹配成功则返回一个匹配对象,否则返回 None

1
2
3
4
5
6
7
8
import re

# 尝试完全匹配字符串与正则表达式模式
result = re.fullmatch(r'\d+', '123')
if result:
print("匹配成功:", result.group()) # 输出: 123
else:
print("匹配失败")

buildin

方法

dir() 函数返回对象的所有属性和方法的列表。

vars() 函数用于返回对象的 __dict__ 属性,如果存在的话,否则会引发 TypeError

属性

__dict__

__dict__它是一个字典,包含了类的命名空间中的所有属性和方法。

当你访问一个类的 __dict__ 属性时,你会得到一个字典,其中包含了该类定义的所有属性和方法的名称及其对应的值。这包括类变量、实例变量、类方法、静态方法、实例方法等。

__doc__

__doc__ 是一个特殊的属性,用于存储文档字符串(docstring)。文档字符串是对 Python 对象(如模块、类、函数或方法)的描述性文本,通常用于说明对象的作用、用法、参数、返回值等信息。

当你定义一个类、函数或方法时,可以在其顶部编写一个文档字符串,用 '''""" 包围起来,来描述该对象

base64

可以进行base64,base32,base16(hex),a85等多种编码解码

以base64为例

  1. 编码(将二进制数据转换为 Base64 字符串)

    使用 base64.b64encode(data) 方法可以将二进制数据编码为 Base64 字符串。其中 data 是要编码的二进制数据。

    1
    2
    3
    4
    import base64

    data = b'Hello, World!'
    encoded_data = base64.b64encode(data)
  2. 解码(将 Base64 字符串转换为二进制数据)

    使用 base64.b64decode(encoded_data) 方法可以将 Base64 字符串解码为二进制数据。其中 encoded_data 是要解码的 Base64 字符串。

    1
    2
    3
    4
    import base64

    encoded_data = b'SGVsbG8sIFdvcmxkIQ=='
    decoded_data = base64.b64decode(encoded_data)

    解码后的二进制数据会以字节串的形式返回。