checksec

# file EasiestPrintf
EasiestPrintf: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=61cd88e3d189854473fddf7c0ace6450986e4b02, not stripped
$ checksec -f EasiestPrintf
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH	FORTIFY	Fortified Fortifiable  FILE
Full RELRO      Canary found      NX enabled    No PIE          No RPATH   No RUNPATH   Yes 0		6	EasiestPrintf

可见代码开启了完全只读重定向、栈溢出检查、不可执行栈等。

程序分析

main函数接给出了一个4byte任意地址读取。可以用来获取lib基址。

而后在leave函数中有一个格式化字符串漏洞,执行后直接退出程序。 此处可写入0xA0byte。

 80487ab:	83 c4 10             	add    $0x10,%esp
 80487ae:	c7 85 50 ff ff ff 00 	movl   $0x0,-0xb0(%ebp)
 80487b5:	00 00 00
 80487b8:	eb 49                	jmp    8048803 <leave+0x92>
 80487ba:	8b 85 50 ff ff ff    	mov    -0xb0(%ebp),%eax
 80487c0:	8d 95 54 ff ff ff    	lea    -0xac(%ebp),%edx
 80487c6:	01 d0                	add    %edx,%eax
 80487c8:	83 ec 04             	sub    $0x4,%esp
 80487cb:	6a 01                	push   $0x1
 80487cd:	50                   	push   %eax
 80487ce:	6a 00                	push   $0x0
 80487d0:	e8 bb fd ff ff       	call   8048590 <.plt.got>
 80487d5:	83 c4 10             	add    $0x10,%esp
 80487d8:	83 f8 01             	cmp    $0x1,%eax
 80487db:	74 0a                	je     80487e7 <leave+0x76>
 80487dd:	83 ec 0c             	sub    $0xc,%esp
 80487e0:	6a ff                	push   $0xffffffff
 80487e2:	e8 e9 fd ff ff       	call   80485d0 <.plt.got+0x40>
 80487e7:	8d 95 54 ff ff ff    	lea    -0xac(%ebp),%edx
 80487ed:	8b 85 50 ff ff ff    	mov    -0xb0(%ebp),%eax
 80487f3:	01 d0                	add    %edx,%eax
 80487f5:	0f b6 00             	movzbl (%eax),%eax
 80487f8:	3c 0a                	cmp    $0xa,%al
 80487fa:	74 15                	je     8048811 <leave+0xa0>
 80487fc:	83 85 50 ff ff ff 01 	addl   $0x1,-0xb0(%ebp)
 8048803:	81 bd 50 ff ff ff 9e 	cmpl   $0x9e,-0xb0(%ebp)
 804880a:	00 00 00
 804880d:	7e ab                	jle    80487ba <leave+0x49>
 804880f:	eb 01                	jmp    8048812 <leave+0xa1>
 8048811:	90                   	nop
 8048812:	83 ec 0c             	sub    $0xc,%esp
 8048815:	8d 85 54 ff ff ff    	lea    -0xac(%ebp),%eax
 804881b:	50                   	push   %eax
 804881c:	e8 77 fd ff ff       	call   8048598 <.plt.got+0x8>
 8048821:	83 c4 10             	add    $0x10,%esp
 8048824:	83 ec 0c             	sub    $0xc,%esp
 8048827:	6a 00                	push   $0x0
 8048829:	e8 72 fd ff ff       	call   80485a0 <.plt.got+0x10>

GOT表地址


readelf  -r EasiestPrintf

Relocation section '.rel.dyn' at offset 0x4cc contains 18 entries:
 Offset     Info    Type            Sym.Value  Sym. Name
08049fc4  00001006 R_386_GLOB_DAT    00000000   read@GLIBC_2.0
08049fc8  00001106 R_386_GLOB_DAT    00000000   printf@GLIBC_2.0
08049fcc  00001206 R_386_GLOB_DAT    00000000   _exit@GLIBC_2.0
08049fd0  00000b06 R_386_GLOB_DAT    00000000   sleep@GLIBC_2.0
08049fd4  00000806 R_386_GLOB_DAT    00000000   alarm@GLIBC_2.0
08049fd8  00000e06 R_386_GLOB_DAT    00000000   __stack_chk_fail@GLIBC_2.4
08049fdc  00000c06 R_386_GLOB_DAT    00000000   puts@GLIBC_2.0
08049fe0  00000106 R_386_GLOB_DAT    00000000   __gmon_start__
08049fe4  00000606 R_386_GLOB_DAT    00000000   exit@GLIBC_2.0
08049fe8  00000206 R_386_GLOB_DAT    00000000   open@GLIBC_2.0
08049fec  00000906 R_386_GLOB_DAT    00000000   __libc_start_main@GLIBC_2.0
08049ff0  00000506 R_386_GLOB_DAT    00000000   setvbuf@GLIBC_2.0
08049ff4  00000306 R_386_GLOB_DAT    00000000   memset@GLIBC_2.0
08049ff8  00000406 R_386_GLOB_DAT    00000000   __isoc99_scanf@GLIBC_2.7
08049ffc  00000f06 R_386_GLOB_DAT    00000000   close@GLIBC_2.0
0804a020  00000a05 R_386_COPY        0804a020   stderr@GLIBC_2.0
0804a040  00000d05 R_386_COPY        0804a040   stdin@GLIBC_2.0
0804a044  00001305 R_386_COPY        0804a044   stdout@GLIBC_2.0

测试格式化字符串


root@shell:~/data/CTF/0ctf2017/pwn_01# ./EasiestPrintf
Which address you wanna read:
134520772
0xf76b8b80
Good Bye
%x %x %x %x %x %x
ffe9478e 1 1 a f7795000 12

gdb-peda$ context_stack 
[------------------------------------stack-------------------------------------]
0000| 0xffffd2b0 --> 0xffffd2cc --> 0xa7825 ('%x\n')
0004| 0xffffd2b4 --> 0xffffd2ce --> 0xa ('\n')
0008| 0xffffd2b8 --> 0x1 
0012| 0xffffd2bc --> 0xf7e834ed (<_IO_do_write+29>:	cmp    ebx,eax)
0016| 0xffffd2c0 --> 0xf7fcad60 --> 0xfbad2887 
0020| 0xffffd2c4 --> 0x80489c0 ("Which address you wanna read:")
0024| 0xffffd2c8 --> 0x2 
0028| 0xffffd2cc --> 0xa7825 ('%x\n')

可见,printf格式化参数可控,且修改字符串头部可以控制参数。

配合%p参数,可以实现任意地址写入1~2byte数据。

比赛时想通过修改got表修改exit函数地址实现利用。 但是没注意到程序开启了Full RELRO。 此时GOT表不可修改。而exit函数本身没有可劫持程序执行流的地方,只好放弃。

看到writeup才知道问题出在printf函数。 在printf函数处理完字符串后修改用到的虚表实现劫持。